praisonaiagents 0.0.141__py3-none-any.whl → 0.0.143__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.
- praisonaiagents/__init__.py +12 -3
- praisonaiagents/agent/agent.py +32 -5
- praisonaiagents/agent/image_agent.py +19 -4
- praisonaiagents/knowledge/knowledge.py +360 -1
- praisonaiagents/llm/llm.py +45 -8
- praisonaiagents/llm/openai_client.py +144 -0
- praisonaiagents/mcp/mcp.py +54 -14
- praisonaiagents/memory/memory.py +390 -12
- praisonaiagents/telemetry/__init__.py +9 -2
- praisonaiagents/telemetry/telemetry.py +255 -25
- praisonaiagents/tools/__init__.py +17 -1
- praisonaiagents/tools/mongodb_tools.py +610 -0
- {praisonaiagents-0.0.141.dist-info → praisonaiagents-0.0.143.dist-info}/METADATA +5 -1
- {praisonaiagents-0.0.141.dist-info → praisonaiagents-0.0.143.dist-info}/RECORD +16 -15
- {praisonaiagents-0.0.141.dist-info → praisonaiagents-0.0.143.dist-info}/WHEEL +0 -0
- {praisonaiagents-0.0.141.dist-info → praisonaiagents-0.0.143.dist-info}/top_level.txt +0 -0
praisonaiagents/memory/memory.py
CHANGED
@@ -5,6 +5,7 @@ import time
|
|
5
5
|
import shutil
|
6
6
|
from typing import Any, Dict, List, Optional, Union, Literal
|
7
7
|
import logging
|
8
|
+
from datetime import datetime
|
8
9
|
|
9
10
|
# Disable litellm telemetry before any imports
|
10
11
|
os.environ["LITELLM_TELEMETRY"] = "False"
|
@@ -39,6 +40,13 @@ try:
|
|
39
40
|
except ImportError:
|
40
41
|
LITELLM_AVAILABLE = False
|
41
42
|
|
43
|
+
try:
|
44
|
+
import pymongo
|
45
|
+
from pymongo import MongoClient
|
46
|
+
PYMONGO_AVAILABLE = True
|
47
|
+
except ImportError:
|
48
|
+
PYMONGO_AVAILABLE = False
|
49
|
+
|
42
50
|
|
43
51
|
|
44
52
|
|
@@ -55,7 +63,7 @@ class Memory:
|
|
55
63
|
|
56
64
|
Config example:
|
57
65
|
{
|
58
|
-
"provider": "rag" or "mem0" or "none",
|
66
|
+
"provider": "rag" or "mem0" or "mongodb" or "none",
|
59
67
|
"use_embedding": True,
|
60
68
|
"short_db": "short_term.db",
|
61
69
|
"long_db": "long_term.db",
|
@@ -65,6 +73,15 @@ class Memory:
|
|
65
73
|
"org_id": "...",
|
66
74
|
"project_id": "...",
|
67
75
|
|
76
|
+
# MongoDB configuration (if provider is "mongodb")
|
77
|
+
"connection_string": "mongodb://localhost:27017/" or "mongodb+srv://user:pass@cluster.mongodb.net/",
|
78
|
+
"database": "praisonai",
|
79
|
+
"use_vector_search": True, # Enable Atlas Vector Search
|
80
|
+
"max_pool_size": 50,
|
81
|
+
"min_pool_size": 10,
|
82
|
+
"max_idle_time": 30000,
|
83
|
+
"server_selection_timeout": 5000,
|
84
|
+
|
68
85
|
# Graph memory configuration (optional)
|
69
86
|
"graph_store": {
|
70
87
|
"provider": "neo4j" or "memgraph",
|
@@ -115,6 +132,7 @@ class Memory:
|
|
115
132
|
self.provider = self.cfg.get("provider", "rag")
|
116
133
|
self.use_mem0 = (self.provider.lower() == "mem0") and MEM0_AVAILABLE
|
117
134
|
self.use_rag = (self.provider.lower() == "rag") and CHROMADB_AVAILABLE and self.cfg.get("use_embedding", False)
|
135
|
+
self.use_mongodb = (self.provider.lower() == "mongodb") and PYMONGO_AVAILABLE
|
118
136
|
self.graph_enabled = False # Initialize graph support flag
|
119
137
|
|
120
138
|
# Extract embedding model from config
|
@@ -127,6 +145,10 @@ class Memory:
|
|
127
145
|
|
128
146
|
self._log_verbose(f"Using embedding model: {self.embedding_model}")
|
129
147
|
|
148
|
+
# Determine embedding dimensions based on model
|
149
|
+
self.embedding_dimensions = self._get_embedding_dimensions(self.embedding_model)
|
150
|
+
self._log_verbose(f"Using embedding dimensions: {self.embedding_dimensions}")
|
151
|
+
|
130
152
|
# Create .praison directory if it doesn't exist
|
131
153
|
os.makedirs(".praison", exist_ok=True)
|
132
154
|
|
@@ -138,9 +160,11 @@ class Memory:
|
|
138
160
|
self.long_db = self.cfg.get("long_db", ".praison/long_term.db")
|
139
161
|
self._init_ltm()
|
140
162
|
|
141
|
-
# Conditionally init Mem0 or local RAG
|
163
|
+
# Conditionally init Mem0, MongoDB, or local RAG
|
142
164
|
if self.use_mem0:
|
143
165
|
self._init_mem0()
|
166
|
+
elif self.use_mongodb:
|
167
|
+
self._init_mongodb()
|
144
168
|
elif self.use_rag:
|
145
169
|
self._init_chroma()
|
146
170
|
|
@@ -261,6 +285,143 @@ class Memory:
|
|
261
285
|
self._log_verbose(f"Failed to initialize ChromaDB: {e}", logging.ERROR)
|
262
286
|
self.use_rag = False
|
263
287
|
|
288
|
+
def _init_mongodb(self):
|
289
|
+
"""Initialize MongoDB client for memory storage."""
|
290
|
+
try:
|
291
|
+
mongo_cfg = self.cfg.get("config", {})
|
292
|
+
self.connection_string = mongo_cfg.get("connection_string", "mongodb://localhost:27017/")
|
293
|
+
self.database_name = mongo_cfg.get("database", "praisonai")
|
294
|
+
self.use_vector_search = mongo_cfg.get("use_vector_search", False)
|
295
|
+
|
296
|
+
# Initialize MongoDB client
|
297
|
+
self.mongo_client = MongoClient(
|
298
|
+
self.connection_string,
|
299
|
+
maxPoolSize=mongo_cfg.get("max_pool_size", 50),
|
300
|
+
minPoolSize=mongo_cfg.get("min_pool_size", 10),
|
301
|
+
maxIdleTimeMS=mongo_cfg.get("max_idle_time", 30000),
|
302
|
+
serverSelectionTimeoutMS=mongo_cfg.get("server_selection_timeout", 5000),
|
303
|
+
retryWrites=True,
|
304
|
+
retryReads=True
|
305
|
+
)
|
306
|
+
|
307
|
+
# Test connection
|
308
|
+
self.mongo_client.admin.command('ping')
|
309
|
+
|
310
|
+
# Setup database and collections
|
311
|
+
self.mongo_db = self.mongo_client[self.database_name]
|
312
|
+
self.mongo_short_term = self.mongo_db.short_term_memory
|
313
|
+
self.mongo_long_term = self.mongo_db.long_term_memory
|
314
|
+
self.mongo_entities = self.mongo_db.entity_memory
|
315
|
+
self.mongo_users = self.mongo_db.user_memory
|
316
|
+
|
317
|
+
# Create indexes for better performance
|
318
|
+
self._create_mongodb_indexes()
|
319
|
+
|
320
|
+
self._log_verbose("MongoDB initialized successfully")
|
321
|
+
|
322
|
+
except Exception as e:
|
323
|
+
self._log_verbose(f"Failed to initialize MongoDB: {e}", logging.ERROR)
|
324
|
+
self.use_mongodb = False
|
325
|
+
|
326
|
+
def _create_mongodb_indexes(self):
|
327
|
+
"""Create MongoDB indexes for better performance."""
|
328
|
+
try:
|
329
|
+
# Text search indexes
|
330
|
+
self.mongo_short_term.create_index([("content", "text")])
|
331
|
+
self.mongo_long_term.create_index([("content", "text")])
|
332
|
+
|
333
|
+
# Compound indexes for filtering
|
334
|
+
self.mongo_short_term.create_index([("created_at", -1), ("metadata.quality", -1)])
|
335
|
+
self.mongo_long_term.create_index([("created_at", -1), ("metadata.quality", -1)])
|
336
|
+
|
337
|
+
# User-specific indexes
|
338
|
+
self.mongo_users.create_index([("user_id", 1), ("created_at", -1)])
|
339
|
+
|
340
|
+
# Entity indexes
|
341
|
+
self.mongo_entities.create_index([("entity_name", 1), ("entity_type", 1)])
|
342
|
+
|
343
|
+
# Vector search indexes for Atlas (if enabled)
|
344
|
+
if self.use_vector_search:
|
345
|
+
self._create_vector_search_indexes()
|
346
|
+
|
347
|
+
except Exception as e:
|
348
|
+
self._log_verbose(f"Warning: Could not create MongoDB indexes: {e}", logging.WARNING)
|
349
|
+
|
350
|
+
def _create_vector_search_indexes(self):
|
351
|
+
"""Create vector search indexes for Atlas."""
|
352
|
+
try:
|
353
|
+
vector_index_def = {
|
354
|
+
"mappings": {
|
355
|
+
"dynamic": True,
|
356
|
+
"fields": {
|
357
|
+
"embedding": {
|
358
|
+
"type": "knnVector",
|
359
|
+
"dimensions": self.embedding_dimensions,
|
360
|
+
"similarity": "cosine"
|
361
|
+
}
|
362
|
+
}
|
363
|
+
}
|
364
|
+
}
|
365
|
+
|
366
|
+
# Create vector indexes for both short and long term collections
|
367
|
+
try:
|
368
|
+
self.mongo_short_term.create_search_index(vector_index_def, "vector_index")
|
369
|
+
self.mongo_long_term.create_search_index(vector_index_def, "vector_index")
|
370
|
+
self._log_verbose("Vector search indexes created successfully")
|
371
|
+
except Exception as e:
|
372
|
+
self._log_verbose(f"Could not create vector search indexes: {e}", logging.WARNING)
|
373
|
+
|
374
|
+
except Exception as e:
|
375
|
+
self._log_verbose(f"Error creating vector search indexes: {e}", logging.WARNING)
|
376
|
+
|
377
|
+
def _get_embedding(self, text: str) -> List[float]:
|
378
|
+
"""Get embedding for text using available embedding services."""
|
379
|
+
try:
|
380
|
+
if LITELLM_AVAILABLE:
|
381
|
+
# Use LiteLLM for consistency with the rest of the codebase
|
382
|
+
import litellm
|
383
|
+
|
384
|
+
response = litellm.embedding(
|
385
|
+
model=self.embedding_model,
|
386
|
+
input=text
|
387
|
+
)
|
388
|
+
return response.data[0]["embedding"]
|
389
|
+
elif OPENAI_AVAILABLE:
|
390
|
+
# Fallback to OpenAI client
|
391
|
+
from openai import OpenAI
|
392
|
+
client = OpenAI()
|
393
|
+
|
394
|
+
response = client.embeddings.create(
|
395
|
+
input=text,
|
396
|
+
model=self.embedding_model
|
397
|
+
)
|
398
|
+
return response.data[0].embedding
|
399
|
+
else:
|
400
|
+
self._log_verbose("Neither litellm nor openai available for embeddings", logging.WARNING)
|
401
|
+
return None
|
402
|
+
except Exception as e:
|
403
|
+
self._log_verbose(f"Error getting embedding: {e}", logging.ERROR)
|
404
|
+
return None
|
405
|
+
|
406
|
+
def _get_embedding_dimensions(self, model_name: str) -> int:
|
407
|
+
"""Get embedding dimensions based on model name."""
|
408
|
+
# Common embedding model dimensions
|
409
|
+
model_dimensions = {
|
410
|
+
"text-embedding-3-small": 1536,
|
411
|
+
"text-embedding-3-large": 3072,
|
412
|
+
"text-embedding-ada-002": 1536,
|
413
|
+
"text-embedding-002": 1536,
|
414
|
+
# Add more models as needed
|
415
|
+
}
|
416
|
+
|
417
|
+
# Check if model name contains known model identifiers
|
418
|
+
for model_key, dimensions in model_dimensions.items():
|
419
|
+
if model_key in model_name.lower():
|
420
|
+
return dimensions
|
421
|
+
|
422
|
+
# Default to 1536 for unknown models (OpenAI standard)
|
423
|
+
return 1536
|
424
|
+
|
264
425
|
# -------------------------------------------------------------------------
|
265
426
|
# Basic Quality Score Computation
|
266
427
|
# -------------------------------------------------------------------------
|
@@ -323,20 +484,40 @@ class Memory:
|
|
323
484
|
)
|
324
485
|
logger.info(f"Processed metadata: {metadata}")
|
325
486
|
|
326
|
-
#
|
487
|
+
# Generate unique ID and timestamp once
|
488
|
+
ident = str(time.time_ns())
|
489
|
+
created_at = time.time()
|
490
|
+
|
491
|
+
# Store in MongoDB if enabled
|
492
|
+
if self.use_mongodb and hasattr(self, "mongo_short_term"):
|
493
|
+
try:
|
494
|
+
doc = {
|
495
|
+
"_id": ident,
|
496
|
+
"content": text,
|
497
|
+
"metadata": metadata,
|
498
|
+
"created_at": datetime.utcnow(),
|
499
|
+
"memory_type": "short_term"
|
500
|
+
}
|
501
|
+
self.mongo_short_term.insert_one(doc)
|
502
|
+
logger.info(f"Successfully stored in MongoDB short-term memory with ID: {ident}")
|
503
|
+
except Exception as e:
|
504
|
+
logger.error(f"Failed to store in MongoDB short-term memory: {e}")
|
505
|
+
raise
|
506
|
+
|
507
|
+
# Existing SQLite store logic
|
327
508
|
try:
|
328
509
|
conn = sqlite3.connect(self.short_db)
|
329
|
-
ident = str(time.time_ns())
|
330
510
|
conn.execute(
|
331
511
|
"INSERT INTO short_mem (id, content, meta, created_at) VALUES (?,?,?,?)",
|
332
|
-
(ident, text, json.dumps(metadata),
|
512
|
+
(ident, text, json.dumps(metadata), created_at)
|
333
513
|
)
|
334
514
|
conn.commit()
|
335
515
|
conn.close()
|
336
|
-
logger.info(f"Successfully stored in short-term memory with ID: {ident}")
|
516
|
+
logger.info(f"Successfully stored in SQLite short-term memory with ID: {ident}")
|
337
517
|
except Exception as e:
|
338
|
-
logger.error(f"Failed to store in short-term memory: {e}")
|
339
|
-
raise
|
518
|
+
logger.error(f"Failed to store in SQLite short-term memory: {e}")
|
519
|
+
if not self.use_mongodb: # Only raise if we're not using MongoDB as fallback
|
520
|
+
raise
|
340
521
|
|
341
522
|
def search_short_term(
|
342
523
|
self,
|
@@ -358,6 +539,67 @@ class Memory:
|
|
358
539
|
filtered = [r for r in results if r.get("score", 1.0) >= relevance_cutoff]
|
359
540
|
return filtered
|
360
541
|
|
542
|
+
elif self.use_mongodb and hasattr(self, "mongo_short_term"):
|
543
|
+
try:
|
544
|
+
results = []
|
545
|
+
|
546
|
+
# If vector search is enabled and we have embeddings
|
547
|
+
if self.use_vector_search and hasattr(self, "_get_embedding"):
|
548
|
+
embedding = self._get_embedding(query)
|
549
|
+
if embedding:
|
550
|
+
# Vector search pipeline
|
551
|
+
pipeline = [
|
552
|
+
{
|
553
|
+
"$vectorSearch": {
|
554
|
+
"index": "vector_index",
|
555
|
+
"path": "embedding",
|
556
|
+
"queryVector": embedding,
|
557
|
+
"numCandidates": limit * 10,
|
558
|
+
"limit": limit
|
559
|
+
}
|
560
|
+
},
|
561
|
+
{
|
562
|
+
"$addFields": {
|
563
|
+
"score": {"$meta": "vectorSearchScore"}
|
564
|
+
}
|
565
|
+
},
|
566
|
+
{
|
567
|
+
"$match": {
|
568
|
+
"metadata.quality": {"$gte": min_quality},
|
569
|
+
"score": {"$gte": relevance_cutoff}
|
570
|
+
}
|
571
|
+
}
|
572
|
+
]
|
573
|
+
|
574
|
+
for doc in self.mongo_short_term.aggregate(pipeline):
|
575
|
+
results.append({
|
576
|
+
"id": str(doc["_id"]),
|
577
|
+
"text": doc["content"],
|
578
|
+
"metadata": doc.get("metadata", {}),
|
579
|
+
"score": doc.get("score", 1.0)
|
580
|
+
})
|
581
|
+
|
582
|
+
# Fallback to text search if no vector results
|
583
|
+
if not results:
|
584
|
+
search_filter = {
|
585
|
+
"$text": {"$search": query},
|
586
|
+
"metadata.quality": {"$gte": min_quality}
|
587
|
+
}
|
588
|
+
|
589
|
+
for doc in self.mongo_short_term.find(search_filter).limit(limit):
|
590
|
+
results.append({
|
591
|
+
"id": str(doc["_id"]),
|
592
|
+
"text": doc["content"],
|
593
|
+
"metadata": doc.get("metadata", {}),
|
594
|
+
"score": 1.0 # Default score for text search
|
595
|
+
})
|
596
|
+
|
597
|
+
return results
|
598
|
+
|
599
|
+
except Exception as e:
|
600
|
+
self._log_verbose(f"Error searching MongoDB short-term memory: {e}", logging.ERROR)
|
601
|
+
return []
|
602
|
+
|
361
603
|
elif self.use_rag and hasattr(self, "chroma_col"):
|
362
604
|
try:
|
363
605
|
if LITELLM_AVAILABLE:
|
@@ -481,6 +723,29 @@ class Memory:
|
|
481
723
|
ident = str(time.time_ns())
|
482
724
|
created = time.time()
|
483
725
|
|
726
|
+
# Store in MongoDB if enabled (first priority)
|
727
|
+
if self.use_mongodb and hasattr(self, "mongo_long_term"):
|
728
|
+
try:
|
729
|
+
doc = {
|
730
|
+
"_id": ident,
|
731
|
+
"content": text,
|
732
|
+
"metadata": metadata,
|
733
|
+
"created_at": datetime.utcnow(),
|
734
|
+
"memory_type": "long_term"
|
735
|
+
}
|
736
|
+
|
737
|
+
# Add embedding if vector search is enabled
|
738
|
+
if self.use_vector_search:
|
739
|
+
embedding = self._get_embedding(text)
|
740
|
+
if embedding:
|
741
|
+
doc["embedding"] = embedding
|
742
|
+
|
743
|
+
self.mongo_long_term.insert_one(doc)
|
744
|
+
logger.info(f"Successfully stored in MongoDB long-term memory with ID: {ident}")
|
745
|
+
except Exception as e:
|
746
|
+
logger.error(f"Failed to store in MongoDB long-term memory: {e}")
|
747
|
+
# Continue to SQLite fallback
|
748
|
+
|
484
749
|
# Store in SQLite
|
485
750
|
try:
|
486
751
|
conn = sqlite3.connect(self.long_db)
|
@@ -493,7 +758,9 @@ class Memory:
|
|
493
758
|
logger.info(f"Successfully stored in SQLite with ID: {ident}")
|
494
759
|
except Exception as e:
|
495
760
|
logger.error(f"Error storing in SQLite: {e}")
|
496
|
-
|
761
|
+
if not (self.use_mongodb and hasattr(self, "mongo_long_term")):
|
762
|
+
# Only raise if MongoDB is not available as fallback
|
763
|
+
return
|
497
764
|
|
498
765
|
# Store in vector database if enabled
|
499
766
|
if self.use_rag and hasattr(self, "chroma_col"):
|
@@ -579,6 +846,76 @@ class Memory:
|
|
579
846
|
logger.info(f"Found {len(filtered)} results in Mem0")
|
580
847
|
return filtered
|
581
848
|
|
849
|
+
elif self.use_mongodb and hasattr(self, "mongo_long_term"):
|
850
|
+
try:
|
851
|
+
results = []
|
852
|
+
|
853
|
+
# If vector search is enabled and we have embeddings
|
854
|
+
if self.use_vector_search:
|
855
|
+
embedding = self._get_embedding(query)
|
856
|
+
if embedding:
|
857
|
+
# Vector search pipeline
|
858
|
+
pipeline = [
|
859
|
+
{
|
860
|
+
"$vectorSearch": {
|
861
|
+
"index": "vector_index",
|
862
|
+
"path": "embedding",
|
863
|
+
"queryVector": embedding,
|
864
|
+
"numCandidates": limit * 10,
|
865
|
+
"limit": limit
|
866
|
+
}
|
867
|
+
},
|
868
|
+
{
|
869
|
+
"$addFields": {
|
870
|
+
"score": {"$meta": "vectorSearchScore"}
|
871
|
+
}
|
872
|
+
},
|
873
|
+
{
|
874
|
+
"$match": {
|
875
|
+
"metadata.quality": {"$gte": min_quality},
|
876
|
+
"score": {"$gte": relevance_cutoff}
|
877
|
+
}
|
878
|
+
}
|
879
|
+
]
|
880
|
+
|
881
|
+
for doc in self.mongo_long_term.aggregate(pipeline):
|
882
|
+
text = doc["content"]
|
883
|
+
# Add memory record citation
|
884
|
+
if "(Memory record:" not in text:
|
885
|
+
text = f"{text} (Memory record: {str(doc['_id'])})"
|
886
|
+
results.append({
|
887
|
+
"id": str(doc["_id"]),
|
888
|
+
"text": text,
|
889
|
+
"metadata": doc.get("metadata", {}),
|
890
|
+
"score": doc.get("score", 1.0)
|
891
|
+
})
|
892
|
+
|
893
|
+
# Fallback to text search if no vector results
|
894
|
+
if not results:
|
895
|
+
search_filter = {
|
896
|
+
"$text": {"$search": query},
|
897
|
+
"metadata.quality": {"$gte": min_quality}
|
898
|
+
}
|
899
|
+
|
900
|
+
for doc in self.mongo_long_term.find(search_filter).limit(limit):
|
901
|
+
text = doc["content"]
|
902
|
+
# Add memory record citation
|
903
|
+
if "(Memory record:" not in text:
|
904
|
+
text = f"{text} (Memory record: {str(doc['_id'])})"
|
905
|
+
results.append({
|
906
|
+
"id": str(doc["_id"]),
|
907
|
+
"text": text,
|
908
|
+
"metadata": doc.get("metadata", {}),
|
909
|
+
"score": 1.0 # Default score for text search
|
910
|
+
})
|
911
|
+
|
912
|
+
logger.info(f"Found {len(results)} results in MongoDB")
|
913
|
+
return results
|
914
|
+
|
915
|
+
except Exception as e:
|
916
|
+
self._log_verbose(f"Error searching MongoDB long-term memory: {e}", logging.ERROR)
|
917
|
+
# Fall through to SQLite search
|
918
|
+
|
582
919
|
elif self.use_rag and hasattr(self, "chroma_col"):
|
583
920
|
try:
|
584
921
|
if LITELLM_AVAILABLE:
|
@@ -617,7 +954,7 @@ class Memory:
|
|
617
954
|
metadata = resp["metadatas"][0][i] if "metadatas" in resp else {}
|
618
955
|
text = resp["documents"][0][i]
|
619
956
|
# Add memory record citation
|
620
|
-
text = f"{text} (Memory record: {
|
957
|
+
text = f"{text} (Memory record: {resp['ids'][0][i]})"
|
621
958
|
found.append({
|
622
959
|
"id": resp["ids"][0][i],
|
623
960
|
"text": text,
|
@@ -673,7 +1010,7 @@ class Memory:
|
|
673
1010
|
return results[:limit]
|
674
1011
|
|
675
1012
|
def reset_long_term(self):
|
676
|
-
"""Clear local LTM DB, plus Chroma or mem0 if in use."""
|
1013
|
+
"""Clear local LTM DB, plus Chroma, MongoDB, or mem0 if in use."""
|
677
1014
|
conn = sqlite3.connect(self.long_db)
|
678
1015
|
conn.execute("DELETE FROM long_mem")
|
679
1016
|
conn.commit()
|
@@ -682,6 +1019,12 @@ class Memory:
|
|
682
1019
|
if self.use_mem0 and hasattr(self, "mem0_client"):
|
683
1020
|
# Mem0 has no universal reset API. Could implement partial or no-op.
|
684
1021
|
pass
|
1022
|
+
if self.use_mongodb and hasattr(self, "mongo_long_term"):
|
1023
|
+
try:
|
1024
|
+
self.mongo_long_term.delete_many({})
|
1025
|
+
self._log_verbose("MongoDB long-term memory cleared")
|
1026
|
+
except Exception as e:
|
1027
|
+
self._log_verbose(f"Error clearing MongoDB long-term memory: {e}", logging.ERROR)
|
685
1028
|
if self.use_rag and hasattr(self, "chroma_client"):
|
686
1029
|
self.chroma_client.reset() # entire DB
|
687
1030
|
self._init_chroma() # re-init fresh
|
@@ -730,6 +1073,21 @@ class Memory:
|
|
730
1073
|
|
731
1074
|
if self.use_mem0 and hasattr(self, "mem0_client"):
|
732
1075
|
self.mem0_client.add(text, user_id=user_id, metadata=meta)
|
1076
|
+
elif self.use_mongodb and hasattr(self, "mongo_users"):
|
1077
|
+
try:
|
1078
|
+
from datetime import datetime
|
1079
|
+
ident = str(time.time_ns())
|
1080
|
+
doc = {
|
1081
|
+
"_id": ident,
|
1082
|
+
"user_id": user_id,
|
1083
|
+
"content": text,
|
1084
|
+
"metadata": meta,
|
1085
|
+
"created_at": datetime.utcnow()
|
1086
|
+
}
|
1087
|
+
self.mongo_users.insert_one(doc)
|
1088
|
+
self._log_verbose(f"Successfully stored user memory for {user_id}")
|
1089
|
+
except Exception as e:
|
1090
|
+
self._log_verbose(f"Error storing user memory: {e}", logging.ERROR)
|
733
1091
|
else:
|
734
1092
|
self.store_long_term(text, metadata=meta)
|
735
1093
|
|
@@ -742,6 +1100,26 @@ class Memory:
|
|
742
1100
|
search_params = {"query": query, "limit": limit, "user_id": user_id, "rerank": rerank}
|
743
1101
|
search_params.update(kwargs)
|
744
1102
|
return self.mem0_client.search(**search_params)
|
1103
|
+
elif self.use_mongodb and hasattr(self, "mongo_users"):
|
1104
|
+
try:
|
1105
|
+
results = []
|
1106
|
+
search_filter = {
|
1107
|
+
"user_id": user_id,
|
1108
|
+
"$text": {"$search": query}
|
1109
|
+
}
|
1110
|
+
|
1111
|
+
for doc in self.mongo_users.find(search_filter).limit(limit):
|
1112
|
+
results.append({
|
1113
|
+
"id": str(doc["_id"]),
|
1114
|
+
"text": doc["content"],
|
1115
|
+
"metadata": doc.get("metadata", {}),
|
1116
|
+
"score": 1.0
|
1117
|
+
})
|
1118
|
+
|
1119
|
+
return results
|
1120
|
+
except Exception as e:
|
1121
|
+
self._log_verbose(f"Error searching MongoDB user memory: {e}", logging.ERROR)
|
1122
|
+
return []
|
745
1123
|
else:
|
746
1124
|
hits = self.search_long_term(query, limit=20)
|
747
1125
|
filtered = []
|
@@ -790,7 +1168,7 @@ class Memory:
|
|
790
1168
|
|
791
1169
|
return self.mem0_client.search(**search_params)
|
792
1170
|
|
793
|
-
# For local memory, use specific search methods
|
1171
|
+
# For MongoDB or local memory, use specific search methods
|
794
1172
|
if user_id:
|
795
1173
|
# Use user-specific search
|
796
1174
|
return self.search_user_memory(user_id, query, limit=limit, rerank=rerank, **kwargs)
|
@@ -24,6 +24,7 @@ __all__ = [
|
|
24
24
|
'get_telemetry',
|
25
25
|
'enable_telemetry',
|
26
26
|
'disable_telemetry',
|
27
|
+
'force_shutdown_telemetry',
|
27
28
|
'MinimalTelemetry',
|
28
29
|
'TelemetryCollector', # For backward compatibility
|
29
30
|
]
|
@@ -47,6 +48,12 @@ def disable_telemetry():
|
|
47
48
|
_disable_telemetry()
|
48
49
|
|
49
50
|
|
51
|
+
def force_shutdown_telemetry():
|
52
|
+
"""Force shutdown of telemetry system with comprehensive cleanup."""
|
53
|
+
from .telemetry import force_shutdown_telemetry as _force_shutdown_telemetry
|
54
|
+
_force_shutdown_telemetry()
|
55
|
+
|
56
|
+
|
50
57
|
# Auto-instrumentation and cleanup setup
|
51
58
|
_initialized = False
|
52
59
|
_atexit_registered = False
|
@@ -65,8 +72,8 @@ def _ensure_atexit():
|
|
65
72
|
])
|
66
73
|
|
67
74
|
if not telemetry_disabled:
|
68
|
-
# Register atexit handler to
|
69
|
-
atexit.register(lambda: get_telemetry().
|
75
|
+
# Register atexit handler to properly shutdown telemetry on exit
|
76
|
+
atexit.register(lambda: get_telemetry().shutdown())
|
70
77
|
_atexit_registered = True
|
71
78
|
|
72
79
|
def _initialize_telemetry():
|