memorisdk 2.1.1__py3-none-any.whl → 2.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of memorisdk might be problematic. Click here for more details.
- memori/__init__.py +1 -1
- memori/agents/conscious_agent.py +5 -4
- memori/agents/memory_agent.py +13 -7
- memori/agents/retrieval_agent.py +49 -17
- memori/core/conversation.py +15 -8
- memori/core/memory.py +117 -45
- memori/database/auto_creator.py +72 -5
- memori/database/mongodb_manager.py +29 -0
- memori/database/search_service.py +265 -121
- memori/database/sqlalchemy_manager.py +94 -13
- memori/integrations/openai_integration.py +12 -27
- memori/utils/logging.py +32 -81
- memori/utils/validators.py +4 -4
- {memorisdk-2.1.1.dist-info → memorisdk-2.3.1.dist-info}/METADATA +21 -7
- {memorisdk-2.1.1.dist-info → memorisdk-2.3.1.dist-info}/RECORD +18 -18
- {memorisdk-2.1.1.dist-info → memorisdk-2.3.1.dist-info}/WHEEL +0 -0
- {memorisdk-2.1.1.dist-info → memorisdk-2.3.1.dist-info}/licenses/LICENSE +0 -0
- {memorisdk-2.1.1.dist-info → memorisdk-2.3.1.dist-info}/top_level.txt +0 -0
memori/__init__.py
CHANGED
|
@@ -5,7 +5,7 @@ Professional-grade memory layer with comprehensive error handling, configuration
|
|
|
5
5
|
management, and modular architecture for production AI systems.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
__version__ = "2.
|
|
8
|
+
__version__ = "2.3.0"
|
|
9
9
|
__author__ = "Harshal More"
|
|
10
10
|
__email__ = "harshalmore2468@gmail.com"
|
|
11
11
|
|
memori/agents/conscious_agent.py
CHANGED
|
@@ -116,7 +116,7 @@ class ConsciouscAgent:
|
|
|
116
116
|
return False
|
|
117
117
|
|
|
118
118
|
async def initialize_existing_conscious_memories(
|
|
119
|
-
self, db_manager, namespace: str = "default"
|
|
119
|
+
self, db_manager, namespace: str = "default", limit: int = 10
|
|
120
120
|
) -> bool:
|
|
121
121
|
"""
|
|
122
122
|
Initialize by copying ALL existing conscious-info memories to short-term memory
|
|
@@ -143,16 +143,17 @@ class ConsciouscAgent:
|
|
|
143
143
|
from sqlalchemy import text
|
|
144
144
|
|
|
145
145
|
with db_manager._get_connection() as connection:
|
|
146
|
-
# Get
|
|
146
|
+
# Get top conscious-info labeled memories from long-term memory (limited for performance)
|
|
147
147
|
cursor = connection.execute(
|
|
148
148
|
text(
|
|
149
149
|
"""SELECT memory_id, processed_data, summary, searchable_content,
|
|
150
150
|
importance_score, created_at
|
|
151
151
|
FROM long_term_memory
|
|
152
152
|
WHERE namespace = :namespace AND classification = 'conscious-info'
|
|
153
|
-
ORDER BY importance_score DESC, created_at DESC
|
|
153
|
+
ORDER BY importance_score DESC, created_at DESC
|
|
154
|
+
LIMIT :limit"""
|
|
154
155
|
),
|
|
155
|
-
{"namespace": namespace},
|
|
156
|
+
{"namespace": namespace, "limit": limit},
|
|
156
157
|
)
|
|
157
158
|
existing_conscious_memories = cursor.fetchall()
|
|
158
159
|
|
memori/agents/memory_agent.py
CHANGED
|
@@ -204,6 +204,9 @@ CONVERSATION CONTEXT:
|
|
|
204
204
|
"content": f"Process this conversation for enhanced memory storage:\n\n{conversation_text}\n{context_info}",
|
|
205
205
|
},
|
|
206
206
|
],
|
|
207
|
+
metadata=[
|
|
208
|
+
"INTERNAL_MEMORY_PROCESSING"
|
|
209
|
+
], # Internal metadata tag
|
|
207
210
|
response_format=ProcessedLongTermMemory,
|
|
208
211
|
temperature=0.1, # Low temperature for consistent processing
|
|
209
212
|
)
|
|
@@ -237,17 +240,19 @@ CONVERSATION CONTEXT:
|
|
|
237
240
|
)
|
|
238
241
|
|
|
239
242
|
logger.debug(
|
|
240
|
-
f"Processed conversation {chat_id}
|
|
241
|
-
f"classification
|
|
242
|
-
f"importance
|
|
243
|
-
f"conscious_context
|
|
244
|
-
f"promotion_eligible
|
|
243
|
+
f"[AGENT] Processed conversation {chat_id[:8]}... - "
|
|
244
|
+
f"classification: {processed_memory.classification.value} | "
|
|
245
|
+
f"importance: {processed_memory.importance.value} | "
|
|
246
|
+
f"conscious_context: {processed_memory.is_user_context} | "
|
|
247
|
+
f"promotion_eligible: {processed_memory.promotion_eligible}"
|
|
245
248
|
)
|
|
246
249
|
|
|
247
250
|
return processed_memory
|
|
248
251
|
|
|
249
252
|
except Exception as e:
|
|
250
|
-
logger.error(
|
|
253
|
+
logger.error(
|
|
254
|
+
f"[AGENT] Memory processing failed for {chat_id[:8]}... - {type(e).__name__}: {e}"
|
|
255
|
+
)
|
|
251
256
|
return self._create_empty_long_term_memory(
|
|
252
257
|
chat_id, f"Processing failed: {str(e)}"
|
|
253
258
|
)
|
|
@@ -307,7 +312,7 @@ CONVERSATION CONTEXT:
|
|
|
307
312
|
|
|
308
313
|
if avg_similarity >= similarity_threshold:
|
|
309
314
|
logger.info(
|
|
310
|
-
f"Duplicate detected
|
|
315
|
+
f"[AGENT] Duplicate detected - {avg_similarity:.2f} similarity with {existing.conversation_id[:8]}..."
|
|
311
316
|
)
|
|
312
317
|
return existing.conversation_id
|
|
313
318
|
|
|
@@ -415,6 +420,7 @@ CONVERSATION CONTEXT:
|
|
|
415
420
|
"content": f"Process this conversation for enhanced memory storage:\n\n{conversation_text}\n{context_info}",
|
|
416
421
|
},
|
|
417
422
|
],
|
|
423
|
+
metadata=["INTERNAL_MEMORY_PROCESSING"], # Internal metadata tag
|
|
418
424
|
temperature=0.1, # Low temperature for consistent processing
|
|
419
425
|
max_tokens=2000, # Ensure enough tokens for full response
|
|
420
426
|
)
|
memori/agents/retrieval_agent.py
CHANGED
|
@@ -149,6 +149,9 @@ Be strategic and comprehensive in your search planning."""
|
|
|
149
149
|
"content": prompt,
|
|
150
150
|
},
|
|
151
151
|
],
|
|
152
|
+
metadata=[
|
|
153
|
+
"INTERNAL_MEMORY_PROCESSING"
|
|
154
|
+
], # Internal metadata tag
|
|
152
155
|
response_format=MemorySearchQuery,
|
|
153
156
|
temperature=0.1,
|
|
154
157
|
)
|
|
@@ -218,15 +221,25 @@ Be strategic and comprehensive in your search planning."""
|
|
|
218
221
|
all_results = []
|
|
219
222
|
seen_memory_ids = set()
|
|
220
223
|
|
|
221
|
-
# For MongoDB and SQL, use
|
|
222
|
-
# This ensures we use the database's native search capabilities
|
|
223
|
-
logger.debug(f"Executing
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
224
|
+
# For MongoDB and SQL, use SearchService directly to avoid recursion
|
|
225
|
+
# This ensures we use the database's native search capabilities without triggering context injection
|
|
226
|
+
logger.debug(f"Executing direct SearchService search using {db_type}")
|
|
227
|
+
try:
|
|
228
|
+
from ..database.search_service import SearchService
|
|
229
|
+
|
|
230
|
+
with db_manager.SessionLocal() as session:
|
|
231
|
+
search_service = SearchService(session, db_type)
|
|
232
|
+
primary_results = search_service.search_memories(
|
|
233
|
+
query=search_plan.query_text or query,
|
|
234
|
+
namespace=namespace,
|
|
235
|
+
limit=limit,
|
|
236
|
+
)
|
|
237
|
+
logger.debug(
|
|
238
|
+
f"Direct SearchService returned {len(primary_results)} results"
|
|
239
|
+
)
|
|
240
|
+
except Exception as e:
|
|
241
|
+
logger.error(f"SearchService direct access failed: {e}")
|
|
242
|
+
primary_results = []
|
|
230
243
|
|
|
231
244
|
# Process primary results and add search metadata
|
|
232
245
|
for result in primary_results:
|
|
@@ -383,9 +396,17 @@ Be strategic and comprehensive in your search planning."""
|
|
|
383
396
|
|
|
384
397
|
search_terms = " ".join(keywords)
|
|
385
398
|
try:
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
399
|
+
# Use SearchService directly to avoid recursion
|
|
400
|
+
from ..database.search_service import SearchService
|
|
401
|
+
|
|
402
|
+
db_type = self._detect_database_type(db_manager)
|
|
403
|
+
|
|
404
|
+
with db_manager.SessionLocal() as session:
|
|
405
|
+
search_service = SearchService(session, db_type)
|
|
406
|
+
results = search_service.search_memories(
|
|
407
|
+
query=search_terms, namespace=namespace, limit=limit
|
|
408
|
+
)
|
|
409
|
+
|
|
389
410
|
# Ensure results is a list of dictionaries
|
|
390
411
|
if not isinstance(results, list):
|
|
391
412
|
logger.warning(f"Search returned non-list result: {type(results)}")
|
|
@@ -417,14 +438,24 @@ Be strategic and comprehensive in your search planning."""
|
|
|
417
438
|
if not categories:
|
|
418
439
|
return []
|
|
419
440
|
|
|
420
|
-
#
|
|
421
|
-
#
|
|
441
|
+
# Use SearchService directly to avoid recursion
|
|
442
|
+
# Get all memories and filter by category
|
|
422
443
|
logger.debug(
|
|
423
444
|
f"Searching memories by categories: {categories} in namespace: {namespace}"
|
|
424
445
|
)
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
446
|
+
try:
|
|
447
|
+
from ..database.search_service import SearchService
|
|
448
|
+
|
|
449
|
+
db_type = self._detect_database_type(db_manager)
|
|
450
|
+
|
|
451
|
+
with db_manager.SessionLocal() as session:
|
|
452
|
+
search_service = SearchService(session, db_type)
|
|
453
|
+
all_results = search_service.search_memories(
|
|
454
|
+
query="", namespace=namespace, limit=limit * 3
|
|
455
|
+
)
|
|
456
|
+
except Exception as e:
|
|
457
|
+
logger.error(f"Category search failed: {e}")
|
|
458
|
+
all_results = []
|
|
428
459
|
|
|
429
460
|
logger.debug(
|
|
430
461
|
f"Retrieved {len(all_results)} total results for category filtering"
|
|
@@ -628,6 +659,7 @@ Be strategic and comprehensive in your search planning."""
|
|
|
628
659
|
"content": prompt,
|
|
629
660
|
},
|
|
630
661
|
],
|
|
662
|
+
metadata=["INTERNAL_MEMORY_PROCESSING"], # Internal metadata tag
|
|
631
663
|
temperature=0.1,
|
|
632
664
|
max_tokens=1000, # Ensure enough tokens for full response
|
|
633
665
|
)
|
memori/core/conversation.py
CHANGED
|
@@ -207,7 +207,7 @@ class ConversationManager:
|
|
|
207
207
|
elif mode == "auto":
|
|
208
208
|
# Auto mode: Search long-term memory database for relevant context
|
|
209
209
|
logger.debug(
|
|
210
|
-
f"Auto-ingest
|
|
210
|
+
f"[CONTEXT] Auto-ingest processing - Query: '{user_input[:50]}...' | Session: {session_id[:8]}..."
|
|
211
211
|
)
|
|
212
212
|
context = (
|
|
213
213
|
memori_instance._get_auto_ingest_context(user_input)
|
|
@@ -217,11 +217,11 @@ class ConversationManager:
|
|
|
217
217
|
if context:
|
|
218
218
|
context_prompt = self._build_auto_context_prompt(context)
|
|
219
219
|
logger.debug(
|
|
220
|
-
f"
|
|
220
|
+
f"[CONTEXT] Long-term memory injected - {len(context)} items | Session: {session_id[:8]}..."
|
|
221
221
|
)
|
|
222
222
|
else:
|
|
223
223
|
logger.debug(
|
|
224
|
-
f"
|
|
224
|
+
f"[CONTEXT] No relevant memories found for '{user_input[:30]}...' | Session: {session_id[:8]}..."
|
|
225
225
|
)
|
|
226
226
|
|
|
227
227
|
# Get conversation history
|
|
@@ -242,11 +242,17 @@ class ConversationManager:
|
|
|
242
242
|
if previous_messages:
|
|
243
243
|
system_content += "\n--- Conversation History ---\n"
|
|
244
244
|
for msg in previous_messages:
|
|
245
|
-
|
|
245
|
+
if msg["role"] == "assistant":
|
|
246
|
+
role_label = "Assistant"
|
|
247
|
+
elif msg["role"] == "user":
|
|
248
|
+
role_label = "User"
|
|
249
|
+
else:
|
|
250
|
+
role_label = msg["role"].capitalize()
|
|
246
251
|
system_content += f"{role_label}: {msg['content']}\n"
|
|
252
|
+
|
|
247
253
|
system_content += "--- End History ---\n"
|
|
248
254
|
logger.debug(
|
|
249
|
-
f"Added {len(previous_messages)} history messages
|
|
255
|
+
f"[CONTEXT] Added {len(previous_messages)} history messages | Session: {session_id[:8]}..."
|
|
250
256
|
)
|
|
251
257
|
|
|
252
258
|
# Find existing system message or create new one
|
|
@@ -267,16 +273,17 @@ class ConversationManager:
|
|
|
267
273
|
0, {"role": "system", "content": system_content}
|
|
268
274
|
)
|
|
269
275
|
|
|
276
|
+
context_status = "yes" if context_prompt else "no"
|
|
277
|
+
history_status = "yes" if len(history_messages) > 1 else "no"
|
|
270
278
|
logger.debug(
|
|
271
|
-
f"Enhanced messages for session {session_id}:
|
|
272
|
-
f"history={'yes' if len(history_messages) > 1 else 'no'}"
|
|
279
|
+
f"[CONTEXT] Enhanced messages for session {session_id[:8]}... - context: {context_status} | history: {history_status}"
|
|
273
280
|
)
|
|
274
281
|
|
|
275
282
|
return enhanced_messages
|
|
276
283
|
|
|
277
284
|
except Exception as e:
|
|
278
285
|
logger.error(
|
|
279
|
-
f"Failed to inject context
|
|
286
|
+
f"[CONTEXT] Failed to inject context for session {session_id[:8]}... - {type(e).__name__}: {e}"
|
|
280
287
|
)
|
|
281
288
|
return messages
|
|
282
289
|
|
memori/core/memory.py
CHANGED
|
@@ -3,6 +3,7 @@ Main Memori class - Pydantic-based memory interface v1.0
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
+
import threading
|
|
6
7
|
import time
|
|
7
8
|
import uuid
|
|
8
9
|
from datetime import datetime
|
|
@@ -14,6 +15,7 @@ try:
|
|
|
14
15
|
import litellm # noqa: F401
|
|
15
16
|
from litellm import success_callback # noqa: F401
|
|
16
17
|
|
|
18
|
+
_ = litellm # Mark as intentionally imported
|
|
17
19
|
LITELLM_AVAILABLE = True
|
|
18
20
|
except ImportError:
|
|
19
21
|
LITELLM_AVAILABLE = False
|
|
@@ -65,6 +67,7 @@ class Memori:
|
|
|
65
67
|
schema_init: bool = True, # Initialize database schema and create tables
|
|
66
68
|
database_prefix: str | None = None, # Database name prefix
|
|
67
69
|
database_suffix: str | None = None, # Database name suffix
|
|
70
|
+
conscious_memory_limit: int = 10, # Limit for conscious memory processing
|
|
68
71
|
):
|
|
69
72
|
"""
|
|
70
73
|
Initialize Memori memory system v1.0.
|
|
@@ -110,6 +113,20 @@ class Memori:
|
|
|
110
113
|
self.database_prefix = database_prefix
|
|
111
114
|
self.database_suffix = database_suffix
|
|
112
115
|
|
|
116
|
+
# Validate conscious_memory_limit parameter
|
|
117
|
+
if not isinstance(conscious_memory_limit, int) or isinstance(
|
|
118
|
+
conscious_memory_limit, bool
|
|
119
|
+
):
|
|
120
|
+
raise TypeError("conscious_memory_limit must be an integer (not bool)")
|
|
121
|
+
|
|
122
|
+
if not (1 <= conscious_memory_limit <= 2000):
|
|
123
|
+
raise ValueError("conscious_memory_limit must be between 1 and 2000")
|
|
124
|
+
|
|
125
|
+
self.conscious_memory_limit = conscious_memory_limit
|
|
126
|
+
|
|
127
|
+
# Thread safety for conscious memory initialization
|
|
128
|
+
self._conscious_init_lock = threading.RLock()
|
|
129
|
+
|
|
113
130
|
# Configure provider based on explicit settings ONLY - no auto-detection
|
|
114
131
|
if provider_config:
|
|
115
132
|
# Use provided configuration
|
|
@@ -452,7 +469,7 @@ class Memori:
|
|
|
452
469
|
)
|
|
453
470
|
init_success = (
|
|
454
471
|
await self.conscious_agent.initialize_existing_conscious_memories(
|
|
455
|
-
self.db_manager, self.namespace
|
|
472
|
+
self.db_manager, self.namespace, self.conscious_memory_limit
|
|
456
473
|
)
|
|
457
474
|
)
|
|
458
475
|
if init_success:
|
|
@@ -478,52 +495,104 @@ class Memori:
|
|
|
478
495
|
|
|
479
496
|
def _run_synchronous_conscious_initialization(self):
|
|
480
497
|
"""Run conscious agent initialization synchronously (when no event loop is available)"""
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
498
|
+
with self._conscious_init_lock:
|
|
499
|
+
try:
|
|
500
|
+
if not self.conscious_agent:
|
|
501
|
+
return
|
|
484
502
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
503
|
+
# Check if we've already initialized in this session to avoid repeated work
|
|
504
|
+
# Use namespace-specific key to prevent conflicts between instances
|
|
505
|
+
init_key = f"_conscious_initialized_{self.namespace or 'default'}"
|
|
506
|
+
if hasattr(self, init_key) and getattr(self, init_key):
|
|
507
|
+
logger.debug(
|
|
508
|
+
f"[CONSCIOUS] Already initialized for namespace '{self.namespace or 'default'}', skipping"
|
|
509
|
+
)
|
|
510
|
+
return
|
|
511
|
+
|
|
512
|
+
# If both auto_ingest and conscious_ingest are enabled,
|
|
513
|
+
# initialize by copying the most important existing conscious-info memories first
|
|
514
|
+
if self.auto_ingest and self.conscious_ingest:
|
|
515
|
+
logger.info(
|
|
516
|
+
"[CONSCIOUS] Both auto_ingest and conscious_ingest enabled - initializing existing conscious memories"
|
|
517
|
+
)
|
|
491
518
|
|
|
492
|
-
|
|
493
|
-
|
|
519
|
+
# Run optimized synchronous initialization of existing memories
|
|
520
|
+
import time
|
|
494
521
|
|
|
495
|
-
|
|
496
|
-
"Conscious-ingest: Synchronous conscious context extraction completed"
|
|
497
|
-
)
|
|
522
|
+
start_time = time.time()
|
|
498
523
|
|
|
499
|
-
|
|
500
|
-
|
|
524
|
+
initialized = self._initialize_existing_conscious_memories_sync()
|
|
525
|
+
|
|
526
|
+
elapsed = time.time() - start_time
|
|
527
|
+
if initialized:
|
|
528
|
+
logger.debug(
|
|
529
|
+
f"[CONSCIOUS] Initialization completed in {elapsed:.2f}s"
|
|
530
|
+
)
|
|
531
|
+
else:
|
|
532
|
+
logger.debug(
|
|
533
|
+
f"[CONSCIOUS] Initialization skipped (no work needed) in {elapsed:.2f}s"
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
# Mark as initialized to avoid repeated work for this specific namespace
|
|
537
|
+
init_key = f"_conscious_initialized_{self.namespace or 'default'}"
|
|
538
|
+
setattr(self, init_key, True)
|
|
539
|
+
|
|
540
|
+
logger.debug(
|
|
541
|
+
"[CONSCIOUS] Synchronous conscious context extraction completed"
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
except Exception as e:
|
|
545
|
+
logger.error(f"Synchronous conscious agent initialization failed: {e}")
|
|
501
546
|
|
|
502
547
|
def _initialize_existing_conscious_memories_sync(self):
|
|
503
|
-
"""Synchronously initialize existing conscious-info memories"""
|
|
548
|
+
"""Synchronously initialize existing conscious-info memories with optimization"""
|
|
504
549
|
try:
|
|
505
550
|
from sqlalchemy import text
|
|
506
551
|
|
|
507
552
|
with self.db_manager._get_connection() as connection:
|
|
508
|
-
#
|
|
553
|
+
# First, check if we already have conscious memories in short-term storage
|
|
554
|
+
existing_short_term = connection.execute(
|
|
555
|
+
text(
|
|
556
|
+
"""SELECT COUNT(*) FROM short_term_memory
|
|
557
|
+
WHERE namespace = :namespace
|
|
558
|
+
AND (category_primary = 'conscious_context' OR memory_id LIKE 'conscious_%')"""
|
|
559
|
+
),
|
|
560
|
+
{"namespace": self.namespace or "default"},
|
|
561
|
+
).scalar()
|
|
562
|
+
|
|
563
|
+
if existing_short_term > 0:
|
|
564
|
+
logger.debug(
|
|
565
|
+
f"[CONSCIOUS] {existing_short_term} conscious memories already in short-term storage, skipping initialization"
|
|
566
|
+
)
|
|
567
|
+
return False
|
|
568
|
+
|
|
569
|
+
# Get only the most important conscious-info memories (limit to 10 for performance)
|
|
509
570
|
cursor = connection.execute(
|
|
510
571
|
text(
|
|
511
572
|
"""SELECT memory_id, processed_data, summary, searchable_content,
|
|
512
573
|
importance_score, created_at
|
|
513
574
|
FROM long_term_memory
|
|
514
575
|
WHERE namespace = :namespace AND classification = 'conscious-info'
|
|
515
|
-
ORDER BY importance_score DESC, created_at DESC
|
|
576
|
+
ORDER BY importance_score DESC, created_at DESC
|
|
577
|
+
LIMIT :limit"""
|
|
516
578
|
),
|
|
517
|
-
{
|
|
579
|
+
{
|
|
580
|
+
"namespace": self.namespace or "default",
|
|
581
|
+
"limit": self.conscious_memory_limit,
|
|
582
|
+
},
|
|
518
583
|
)
|
|
519
584
|
existing_conscious_memories = cursor.fetchall()
|
|
520
585
|
|
|
521
586
|
if not existing_conscious_memories:
|
|
522
587
|
logger.debug(
|
|
523
|
-
"
|
|
588
|
+
"[CONSCIOUS] No conscious-info memories found for initialization"
|
|
524
589
|
)
|
|
525
590
|
return False
|
|
526
591
|
|
|
592
|
+
# Batch process memories for efficiency
|
|
593
|
+
logger.debug(
|
|
594
|
+
f"[CONSCIOUS] Processing {len(existing_conscious_memories)} conscious memories..."
|
|
595
|
+
)
|
|
527
596
|
copied_count = 0
|
|
528
597
|
for memory_row in existing_conscious_memories:
|
|
529
598
|
success = self._copy_memory_to_short_term_sync(memory_row)
|
|
@@ -532,12 +601,12 @@ class Memori:
|
|
|
532
601
|
|
|
533
602
|
if copied_count > 0:
|
|
534
603
|
logger.info(
|
|
535
|
-
f"
|
|
604
|
+
f"[CONSCIOUS] Initialized {copied_count} conscious memories to short-term storage"
|
|
536
605
|
)
|
|
537
606
|
return True
|
|
538
607
|
else:
|
|
539
608
|
logger.debug(
|
|
540
|
-
"
|
|
609
|
+
"[CONSCIOUS] No new conscious memories to initialize (all were duplicates)"
|
|
541
610
|
)
|
|
542
611
|
return False
|
|
543
612
|
|
|
@@ -564,26 +633,25 @@ class Memori:
|
|
|
564
633
|
from sqlalchemy import text
|
|
565
634
|
|
|
566
635
|
with self.db_manager._get_connection() as connection:
|
|
567
|
-
#
|
|
636
|
+
# Database-agnostic duplicate check with safer pattern matching
|
|
568
637
|
existing_check = connection.execute(
|
|
569
638
|
text(
|
|
570
639
|
"""SELECT COUNT(*) FROM short_term_memory
|
|
571
640
|
WHERE namespace = :namespace
|
|
572
|
-
AND
|
|
573
|
-
|
|
574
|
-
OR summary = :summary)"""
|
|
641
|
+
AND (memory_id = :exact_id
|
|
642
|
+
OR memory_id LIKE :conscious_pattern)"""
|
|
575
643
|
),
|
|
576
644
|
{
|
|
577
645
|
"namespace": self.namespace or "default",
|
|
578
|
-
"
|
|
579
|
-
"
|
|
646
|
+
"exact_id": memory_id,
|
|
647
|
+
"conscious_pattern": f"conscious_{memory_id}_%",
|
|
580
648
|
},
|
|
581
649
|
)
|
|
582
650
|
|
|
583
651
|
existing_count = existing_check.scalar()
|
|
584
652
|
if existing_count > 0:
|
|
585
653
|
logger.debug(
|
|
586
|
-
f"
|
|
654
|
+
f"[CONSCIOUS] Skipping duplicate memory {memory_id[:8]}... - already exists in short-term memory"
|
|
587
655
|
)
|
|
588
656
|
return False
|
|
589
657
|
|
|
@@ -1152,7 +1220,7 @@ class Memori:
|
|
|
1152
1220
|
results[:3]
|
|
1153
1221
|
): # Log first 3 results for debugging
|
|
1154
1222
|
logger.debug(
|
|
1155
|
-
f"Auto-ingest: Result {i+1}: {type(result)} with keys: {list(result.keys()) if isinstance(result, dict) else 'N/A'}"
|
|
1223
|
+
f"Auto-ingest: Result {i + 1}: {type(result)} with keys: {list(result.keys()) if isinstance(result, dict) else 'N/A'}"
|
|
1156
1224
|
)
|
|
1157
1225
|
except Exception as db_search_e:
|
|
1158
1226
|
logger.error(f"Auto-ingest: Database search failed: {db_search_e}")
|
|
@@ -1892,7 +1960,7 @@ class Memori:
|
|
|
1892
1960
|
|
|
1893
1961
|
# Debug logging for conversation recording
|
|
1894
1962
|
logger.info(
|
|
1895
|
-
f"Recording conversation - Input: '{user_input[:
|
|
1963
|
+
f"[MEMORY] Recording conversation - Input: '{user_input[:60]}...' | Model: {model} | Session: {self.session_id[:8]}..."
|
|
1896
1964
|
)
|
|
1897
1965
|
|
|
1898
1966
|
# Parse response
|
|
@@ -1915,29 +1983,31 @@ class Memori:
|
|
|
1915
1983
|
namespace=self.namespace,
|
|
1916
1984
|
metadata=metadata or {},
|
|
1917
1985
|
)
|
|
1918
|
-
logger.debug(
|
|
1919
|
-
f"Successfully stored chat history for conversation: {chat_id}"
|
|
1920
|
-
)
|
|
1986
|
+
logger.debug(f"[MEMORY] Chat history stored - ID: {chat_id[:8]}...")
|
|
1921
1987
|
|
|
1922
1988
|
# Always process into long-term memory when memory agent is available
|
|
1923
1989
|
if self.memory_agent:
|
|
1924
1990
|
self._schedule_memory_processing(
|
|
1925
1991
|
chat_id, user_input, response_text, response_model
|
|
1926
1992
|
)
|
|
1927
|
-
logger.debug(f"
|
|
1993
|
+
logger.debug(f"[MEMORY] Processing scheduled - ID: {chat_id[:8]}...")
|
|
1928
1994
|
else:
|
|
1929
1995
|
logger.warning(
|
|
1930
|
-
f"
|
|
1996
|
+
f"[MEMORY] Agent unavailable, skipping processing - ID: {chat_id[:8]}..."
|
|
1931
1997
|
)
|
|
1932
1998
|
|
|
1933
|
-
logger.info(
|
|
1999
|
+
logger.info(
|
|
2000
|
+
f"[MEMORY] Conversation recorded successfully - ID: {chat_id[:8]}..."
|
|
2001
|
+
)
|
|
1934
2002
|
return chat_id
|
|
1935
2003
|
|
|
1936
2004
|
except Exception as e:
|
|
1937
|
-
logger.error(
|
|
2005
|
+
logger.error(
|
|
2006
|
+
f"[MEMORY] Failed to record conversation {chat_id[:8]}... - {type(e).__name__}: {e}"
|
|
2007
|
+
)
|
|
1938
2008
|
import traceback
|
|
1939
2009
|
|
|
1940
|
-
logger.
|
|
2010
|
+
logger.debug(f"[MEMORY] Recording error details: {traceback.format_exc()}")
|
|
1941
2011
|
raise
|
|
1942
2012
|
|
|
1943
2013
|
def _schedule_memory_processing(
|
|
@@ -2571,12 +2641,14 @@ class Memori:
|
|
|
2571
2641
|
Get auto-ingest context as system prompt for direct injection.
|
|
2572
2642
|
Returns relevant memories based on user input as formatted system prompt.
|
|
2573
2643
|
Use this for auto_ingest mode.
|
|
2644
|
+
|
|
2645
|
+
Note: Context retrieval is handled by _get_auto_ingest_context().
|
|
2646
|
+
This function only formats pre-retrieved context.
|
|
2574
2647
|
"""
|
|
2575
2648
|
try:
|
|
2576
|
-
#
|
|
2577
|
-
#
|
|
2578
|
-
|
|
2579
|
-
context = self._get_conscious_context() # Get recent short-term memories
|
|
2649
|
+
# Get recent short-term memories as fallback context
|
|
2650
|
+
# The actual intelligent retrieval is handled by _get_auto_ingest_context()
|
|
2651
|
+
context = self._get_conscious_context()
|
|
2580
2652
|
|
|
2581
2653
|
if not context:
|
|
2582
2654
|
return ""
|