memorisdk 2.1.0__tar.gz → 2.3.0__tar.gz
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.
- {memorisdk-2.1.0 → memorisdk-2.3.0}/PKG-INFO +5 -3
- {memorisdk-2.1.0 → memorisdk-2.3.0}/README.md +1 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/__init__.py +1 -1
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/agents/conscious_agent.py +5 -4
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/agents/memory_agent.py +9 -7
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/agents/retrieval_agent.py +45 -17
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/core/conversation.py +8 -7
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/core/memory.py +103 -40
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/auto_creator.py +72 -5
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/connectors/mongodb_connector.py +149 -22
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/mongodb_manager.py +116 -34
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/search_service.py +265 -121
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/sqlalchemy_manager.py +61 -13
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memorisdk.egg-info/PKG-INFO +5 -3
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memorisdk.egg-info/requires.txt +3 -2
- {memorisdk-2.1.0 → memorisdk-2.3.0}/pyproject.toml +4 -3
- {memorisdk-2.1.0 → memorisdk-2.3.0}/LICENSE +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/agents/__init__.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/config/__init__.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/config/manager.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/config/memory_manager.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/config/settings.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/core/__init__.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/core/database.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/core/providers.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/__init__.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/adapters/__init__.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/adapters/mongodb_adapter.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/adapters/mysql_adapter.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/adapters/postgresql_adapter.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/adapters/sqlite_adapter.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/connection_utils.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/connectors/__init__.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/connectors/base_connector.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/connectors/mysql_connector.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/connectors/postgres_connector.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/connectors/sqlite_connector.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/models.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/queries/__init__.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/queries/base_queries.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/queries/chat_queries.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/queries/entity_queries.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/queries/memory_queries.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/query_translator.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/schema_generators/__init__.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/schema_generators/mongodb_schema_generator.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/schema_generators/mysql_schema_generator.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/search/__init__.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/search/mongodb_search_adapter.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/search/mysql_search_adapter.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/search/sqlite_search_adapter.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/templates/__init__.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/templates/basic_template.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/database/templates/schemas/__init__.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/integrations/__init__.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/integrations/anthropic_integration.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/integrations/litellm_integration.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/integrations/openai_integration.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/tools/__init__.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/tools/memory_tool.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/utils/__init__.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/utils/exceptions.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/utils/helpers.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/utils/input_validator.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/utils/logging.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/utils/pydantic_models.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/utils/query_builder.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/utils/schemas.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/utils/security_audit.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/utils/security_integration.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/utils/transaction_manager.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memori/utils/validators.py +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memorisdk.egg-info/SOURCES.txt +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memorisdk.egg-info/dependency_links.txt +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/memorisdk.egg-info/top_level.txt +0 -0
- {memorisdk-2.1.0 → memorisdk-2.3.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: memorisdk
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: The Open-Source Memory Layer for AI Agents & Multi-Agent Systems
|
|
5
5
|
Author-email: GibsonAI Team <noc@gibsonai.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -53,11 +53,11 @@ Requires-Dist: psycopg2-binary>=2.9.0; extra == "postgres"
|
|
|
53
53
|
Provides-Extra: mysql
|
|
54
54
|
Requires-Dist: PyMySQL>=1.0.0; extra == "mysql"
|
|
55
55
|
Provides-Extra: mongodb
|
|
56
|
-
Requires-Dist: pymongo>=4.0.0; extra == "mongodb"
|
|
56
|
+
Requires-Dist: pymongo[srv]>=4.0.0; extra == "mongodb"
|
|
57
57
|
Provides-Extra: databases
|
|
58
58
|
Requires-Dist: psycopg2-binary>=2.9.0; extra == "databases"
|
|
59
59
|
Requires-Dist: PyMySQL>=1.0.0; extra == "databases"
|
|
60
|
-
Requires-Dist: pymongo>=4.0.0; extra == "databases"
|
|
60
|
+
Requires-Dist: pymongo[srv]>=4.0.0; extra == "databases"
|
|
61
61
|
Provides-Extra: anthropic
|
|
62
62
|
Requires-Dist: anthropic>=0.3.0; extra == "anthropic"
|
|
63
63
|
Provides-Extra: litellm
|
|
@@ -87,6 +87,7 @@ Requires-Dist: mkdocs-minify-plugin>=0.7.0; extra == "all"
|
|
|
87
87
|
Requires-Dist: mkdocs-redirects>=1.2.0; extra == "all"
|
|
88
88
|
Requires-Dist: psycopg2-binary>=2.9.0; extra == "all"
|
|
89
89
|
Requires-Dist: PyMySQL>=1.0.0; extra == "all"
|
|
90
|
+
Requires-Dist: pymongo[srv]>=4.0.0; extra == "all"
|
|
90
91
|
Requires-Dist: litellm>=1.0.0; extra == "all"
|
|
91
92
|
Requires-Dist: anthropic>=0.3.0; extra == "all"
|
|
92
93
|
Requires-Dist: streamlit>=1.28.0; extra == "all"
|
|
@@ -481,6 +482,7 @@ Memori works seamlessly with popular AI frameworks:
|
|
|
481
482
|
| [Agno](./examples/integrations/agno_example.py) | Memory-enhanced agent framework integration with persistent conversations | Simple chat agent with memory search |
|
|
482
483
|
| [AWS Strands](./examples/integrations/aws_strands_example.py) | Professional development coach with Strands SDK and persistent memory | Career coaching agent with goal tracking |
|
|
483
484
|
| [Azure AI Foundry](./examples/integrations/azure_ai_foundry_example.py) | Azure AI Foundry agents with persistent memory across conversations | Enterprise AI agents with Azure integration |
|
|
485
|
+
| [AutoGen](./examples/integrations/autogen_example.py) | Multi-agent group chat memory recording | Agent chats with memory integration |
|
|
484
486
|
| [CamelAI](./examples/integrations/camelai_example.py) | Multi-agent communication framework with automatic memory recording and retrieval | Memory-enhanced chat agents with conversation continuity |
|
|
485
487
|
| [CrewAI](./examples/integrations/crewai_example.py) | Multi-agent system with shared memory across agent interactions | Collaborative agents with memory |
|
|
486
488
|
| [Digital Ocean AI](./examples/integrations/digital_ocean_example.py) | Memory-enhanced customer support using Digital Ocean's AI platform | Customer support assistant with conversation history |
|
|
@@ -385,6 +385,7 @@ Memori works seamlessly with popular AI frameworks:
|
|
|
385
385
|
| [Agno](./examples/integrations/agno_example.py) | Memory-enhanced agent framework integration with persistent conversations | Simple chat agent with memory search |
|
|
386
386
|
| [AWS Strands](./examples/integrations/aws_strands_example.py) | Professional development coach with Strands SDK and persistent memory | Career coaching agent with goal tracking |
|
|
387
387
|
| [Azure AI Foundry](./examples/integrations/azure_ai_foundry_example.py) | Azure AI Foundry agents with persistent memory across conversations | Enterprise AI agents with Azure integration |
|
|
388
|
+
| [AutoGen](./examples/integrations/autogen_example.py) | Multi-agent group chat memory recording | Agent chats with memory integration |
|
|
388
389
|
| [CamelAI](./examples/integrations/camelai_example.py) | Multi-agent communication framework with automatic memory recording and retrieval | Memory-enhanced chat agents with conversation continuity |
|
|
389
390
|
| [CrewAI](./examples/integrations/crewai_example.py) | Multi-agent system with shared memory across agent interactions | Collaborative agents with memory |
|
|
390
391
|
| [Digital Ocean AI](./examples/integrations/digital_ocean_example.py) | Memory-enhanced customer support using Digital Ocean's AI platform | Customer support assistant with conversation history |
|
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -237,17 +237,19 @@ CONVERSATION CONTEXT:
|
|
|
237
237
|
)
|
|
238
238
|
|
|
239
239
|
logger.debug(
|
|
240
|
-
f"Processed conversation {chat_id}
|
|
241
|
-
f"classification
|
|
242
|
-
f"importance
|
|
243
|
-
f"conscious_context
|
|
244
|
-
f"promotion_eligible
|
|
240
|
+
f"[AGENT] Processed conversation {chat_id[:8]}... - "
|
|
241
|
+
f"classification: {processed_memory.classification.value} | "
|
|
242
|
+
f"importance: {processed_memory.importance.value} | "
|
|
243
|
+
f"conscious_context: {processed_memory.is_user_context} | "
|
|
244
|
+
f"promotion_eligible: {processed_memory.promotion_eligible}"
|
|
245
245
|
)
|
|
246
246
|
|
|
247
247
|
return processed_memory
|
|
248
248
|
|
|
249
249
|
except Exception as e:
|
|
250
|
-
logger.error(
|
|
250
|
+
logger.error(
|
|
251
|
+
f"[AGENT] Memory processing failed for {chat_id[:8]}... - {type(e).__name__}: {e}"
|
|
252
|
+
)
|
|
251
253
|
return self._create_empty_long_term_memory(
|
|
252
254
|
chat_id, f"Processing failed: {str(e)}"
|
|
253
255
|
)
|
|
@@ -307,7 +309,7 @@ CONVERSATION CONTEXT:
|
|
|
307
309
|
|
|
308
310
|
if avg_similarity >= similarity_threshold:
|
|
309
311
|
logger.info(
|
|
310
|
-
f"Duplicate detected
|
|
312
|
+
f"[AGENT] Duplicate detected - {avg_similarity:.2f} similarity with {existing.conversation_id[:8]}..."
|
|
311
313
|
)
|
|
312
314
|
return existing.conversation_id
|
|
313
315
|
|
|
@@ -218,15 +218,25 @@ Be strategic and comprehensive in your search planning."""
|
|
|
218
218
|
all_results = []
|
|
219
219
|
seen_memory_ids = set()
|
|
220
220
|
|
|
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
|
-
|
|
221
|
+
# For MongoDB and SQL, use SearchService directly to avoid recursion
|
|
222
|
+
# This ensures we use the database's native search capabilities without triggering context injection
|
|
223
|
+
logger.debug(f"Executing direct SearchService search using {db_type}")
|
|
224
|
+
try:
|
|
225
|
+
from ..database.search_service import SearchService
|
|
226
|
+
|
|
227
|
+
with db_manager.SessionLocal() as session:
|
|
228
|
+
search_service = SearchService(session, db_type)
|
|
229
|
+
primary_results = search_service.search_memories(
|
|
230
|
+
query=search_plan.query_text or query,
|
|
231
|
+
namespace=namespace,
|
|
232
|
+
limit=limit,
|
|
233
|
+
)
|
|
234
|
+
logger.debug(
|
|
235
|
+
f"Direct SearchService returned {len(primary_results)} results"
|
|
236
|
+
)
|
|
237
|
+
except Exception as e:
|
|
238
|
+
logger.error(f"SearchService direct access failed: {e}")
|
|
239
|
+
primary_results = []
|
|
230
240
|
|
|
231
241
|
# Process primary results and add search metadata
|
|
232
242
|
for result in primary_results:
|
|
@@ -383,9 +393,17 @@ Be strategic and comprehensive in your search planning."""
|
|
|
383
393
|
|
|
384
394
|
search_terms = " ".join(keywords)
|
|
385
395
|
try:
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
396
|
+
# Use SearchService directly to avoid recursion
|
|
397
|
+
from ..database.search_service import SearchService
|
|
398
|
+
|
|
399
|
+
db_type = self._detect_database_type(db_manager)
|
|
400
|
+
|
|
401
|
+
with db_manager.SessionLocal() as session:
|
|
402
|
+
search_service = SearchService(session, db_type)
|
|
403
|
+
results = search_service.search_memories(
|
|
404
|
+
query=search_terms, namespace=namespace, limit=limit
|
|
405
|
+
)
|
|
406
|
+
|
|
389
407
|
# Ensure results is a list of dictionaries
|
|
390
408
|
if not isinstance(results, list):
|
|
391
409
|
logger.warning(f"Search returned non-list result: {type(results)}")
|
|
@@ -417,14 +435,24 @@ Be strategic and comprehensive in your search planning."""
|
|
|
417
435
|
if not categories:
|
|
418
436
|
return []
|
|
419
437
|
|
|
420
|
-
#
|
|
421
|
-
#
|
|
438
|
+
# Use SearchService directly to avoid recursion
|
|
439
|
+
# Get all memories and filter by category
|
|
422
440
|
logger.debug(
|
|
423
441
|
f"Searching memories by categories: {categories} in namespace: {namespace}"
|
|
424
442
|
)
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
443
|
+
try:
|
|
444
|
+
from ..database.search_service import SearchService
|
|
445
|
+
|
|
446
|
+
db_type = self._detect_database_type(db_manager)
|
|
447
|
+
|
|
448
|
+
with db_manager.SessionLocal() as session:
|
|
449
|
+
search_service = SearchService(session, db_type)
|
|
450
|
+
all_results = search_service.search_memories(
|
|
451
|
+
query="", namespace=namespace, limit=limit * 3
|
|
452
|
+
)
|
|
453
|
+
except Exception as e:
|
|
454
|
+
logger.error(f"Category search failed: {e}")
|
|
455
|
+
all_results = []
|
|
428
456
|
|
|
429
457
|
logger.debug(
|
|
430
458
|
f"Retrieved {len(all_results)} total results for category filtering"
|
|
@@ -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
|
|
@@ -246,7 +246,7 @@ class ConversationManager:
|
|
|
246
246
|
system_content += f"{role_label}: {msg['content']}\n"
|
|
247
247
|
system_content += "--- End History ---\n"
|
|
248
248
|
logger.debug(
|
|
249
|
-
f"Added {len(previous_messages)} history messages
|
|
249
|
+
f"[CONTEXT] Added {len(previous_messages)} history messages | Session: {session_id[:8]}..."
|
|
250
250
|
)
|
|
251
251
|
|
|
252
252
|
# Find existing system message or create new one
|
|
@@ -267,16 +267,17 @@ class ConversationManager:
|
|
|
267
267
|
0, {"role": "system", "content": system_content}
|
|
268
268
|
)
|
|
269
269
|
|
|
270
|
+
context_status = "yes" if context_prompt else "no"
|
|
271
|
+
history_status = "yes" if len(history_messages) > 1 else "no"
|
|
270
272
|
logger.debug(
|
|
271
|
-
f"Enhanced messages for session {session_id}:
|
|
272
|
-
f"history={'yes' if len(history_messages) > 1 else 'no'}"
|
|
273
|
+
f"[CONTEXT] Enhanced messages for session {session_id[:8]}... - context: {context_status} | history: {history_status}"
|
|
273
274
|
)
|
|
274
275
|
|
|
275
276
|
return enhanced_messages
|
|
276
277
|
|
|
277
278
|
except Exception as e:
|
|
278
279
|
logger.error(
|
|
279
|
-
f"Failed to inject context
|
|
280
|
+
f"[CONTEXT] Failed to inject context for session {session_id[:8]}... - {type(e).__name__}: {e}"
|
|
280
281
|
)
|
|
281
282
|
return messages
|
|
282
283
|
|
|
@@ -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
|
|
@@ -65,6 +66,7 @@ class Memori:
|
|
|
65
66
|
schema_init: bool = True, # Initialize database schema and create tables
|
|
66
67
|
database_prefix: str | None = None, # Database name prefix
|
|
67
68
|
database_suffix: str | None = None, # Database name suffix
|
|
69
|
+
conscious_memory_limit: int = 10, # Limit for conscious memory processing
|
|
68
70
|
):
|
|
69
71
|
"""
|
|
70
72
|
Initialize Memori memory system v1.0.
|
|
@@ -109,6 +111,14 @@ class Memori:
|
|
|
109
111
|
self.schema_init = schema_init
|
|
110
112
|
self.database_prefix = database_prefix
|
|
111
113
|
self.database_suffix = database_suffix
|
|
114
|
+
# Validate conscious_memory_limit parameter
|
|
115
|
+
if not isinstance(conscious_memory_limit, int) or conscious_memory_limit < 1:
|
|
116
|
+
raise ValueError("conscious_memory_limit must be a positive integer")
|
|
117
|
+
|
|
118
|
+
self.conscious_memory_limit = conscious_memory_limit
|
|
119
|
+
|
|
120
|
+
# Thread safety for conscious memory initialization
|
|
121
|
+
self._conscious_init_lock = threading.RLock()
|
|
112
122
|
|
|
113
123
|
# Configure provider based on explicit settings ONLY - no auto-detection
|
|
114
124
|
if provider_config:
|
|
@@ -452,7 +462,7 @@ class Memori:
|
|
|
452
462
|
)
|
|
453
463
|
init_success = (
|
|
454
464
|
await self.conscious_agent.initialize_existing_conscious_memories(
|
|
455
|
-
self.db_manager, self.namespace
|
|
465
|
+
self.db_manager, self.namespace, self.conscious_memory_limit
|
|
456
466
|
)
|
|
457
467
|
)
|
|
458
468
|
if init_success:
|
|
@@ -478,52 +488,104 @@ class Memori:
|
|
|
478
488
|
|
|
479
489
|
def _run_synchronous_conscious_initialization(self):
|
|
480
490
|
"""Run conscious agent initialization synchronously (when no event loop is available)"""
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
491
|
+
with self._conscious_init_lock:
|
|
492
|
+
try:
|
|
493
|
+
if not self.conscious_agent:
|
|
494
|
+
return
|
|
484
495
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
496
|
+
# Check if we've already initialized in this session to avoid repeated work
|
|
497
|
+
# Use namespace-specific key to prevent conflicts between instances
|
|
498
|
+
init_key = f"_conscious_initialized_{self.namespace or 'default'}"
|
|
499
|
+
if hasattr(self, init_key) and getattr(self, init_key):
|
|
500
|
+
logger.debug(
|
|
501
|
+
f"[CONSCIOUS] Already initialized for namespace '{self.namespace or 'default'}', skipping"
|
|
502
|
+
)
|
|
503
|
+
return
|
|
491
504
|
|
|
492
|
-
#
|
|
493
|
-
|
|
505
|
+
# If both auto_ingest and conscious_ingest are enabled,
|
|
506
|
+
# initialize by copying the most important existing conscious-info memories first
|
|
507
|
+
if self.auto_ingest and self.conscious_ingest:
|
|
508
|
+
logger.info(
|
|
509
|
+
"[CONSCIOUS] Both auto_ingest and conscious_ingest enabled - initializing existing conscious memories"
|
|
510
|
+
)
|
|
494
511
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
)
|
|
512
|
+
# Run optimized synchronous initialization of existing memories
|
|
513
|
+
import time
|
|
498
514
|
|
|
499
|
-
|
|
500
|
-
|
|
515
|
+
start_time = time.time()
|
|
516
|
+
|
|
517
|
+
initialized = self._initialize_existing_conscious_memories_sync()
|
|
518
|
+
|
|
519
|
+
elapsed = time.time() - start_time
|
|
520
|
+
if initialized:
|
|
521
|
+
logger.debug(
|
|
522
|
+
f"[CONSCIOUS] Initialization completed in {elapsed:.2f}s"
|
|
523
|
+
)
|
|
524
|
+
else:
|
|
525
|
+
logger.debug(
|
|
526
|
+
f"[CONSCIOUS] Initialization skipped (no work needed) in {elapsed:.2f}s"
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
# Mark as initialized to avoid repeated work for this specific namespace
|
|
530
|
+
init_key = f"_conscious_initialized_{self.namespace or 'default'}"
|
|
531
|
+
setattr(self, init_key, True)
|
|
532
|
+
|
|
533
|
+
logger.debug(
|
|
534
|
+
"[CONSCIOUS] Synchronous conscious context extraction completed"
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
except Exception as e:
|
|
538
|
+
logger.error(f"Synchronous conscious agent initialization failed: {e}")
|
|
501
539
|
|
|
502
540
|
def _initialize_existing_conscious_memories_sync(self):
|
|
503
|
-
"""Synchronously initialize existing conscious-info memories"""
|
|
541
|
+
"""Synchronously initialize existing conscious-info memories with optimization"""
|
|
504
542
|
try:
|
|
505
543
|
from sqlalchemy import text
|
|
506
544
|
|
|
507
545
|
with self.db_manager._get_connection() as connection:
|
|
508
|
-
#
|
|
546
|
+
# First, check if we already have conscious memories in short-term storage
|
|
547
|
+
existing_short_term = connection.execute(
|
|
548
|
+
text(
|
|
549
|
+
"""SELECT COUNT(*) FROM short_term_memory
|
|
550
|
+
WHERE namespace = :namespace
|
|
551
|
+
AND (category_primary = 'conscious_context' OR memory_id LIKE 'conscious_%')"""
|
|
552
|
+
),
|
|
553
|
+
{"namespace": self.namespace or "default"},
|
|
554
|
+
).scalar()
|
|
555
|
+
|
|
556
|
+
if existing_short_term > 0:
|
|
557
|
+
logger.debug(
|
|
558
|
+
f"[CONSCIOUS] {existing_short_term} conscious memories already in short-term storage, skipping initialization"
|
|
559
|
+
)
|
|
560
|
+
return False
|
|
561
|
+
|
|
562
|
+
# Get only the most important conscious-info memories (limit to 10 for performance)
|
|
509
563
|
cursor = connection.execute(
|
|
510
564
|
text(
|
|
511
565
|
"""SELECT memory_id, processed_data, summary, searchable_content,
|
|
512
566
|
importance_score, created_at
|
|
513
567
|
FROM long_term_memory
|
|
514
568
|
WHERE namespace = :namespace AND classification = 'conscious-info'
|
|
515
|
-
ORDER BY importance_score DESC, created_at DESC
|
|
569
|
+
ORDER BY importance_score DESC, created_at DESC
|
|
570
|
+
LIMIT :limit"""
|
|
516
571
|
),
|
|
517
|
-
{
|
|
572
|
+
{
|
|
573
|
+
"namespace": self.namespace or "default",
|
|
574
|
+
"limit": self.conscious_memory_limit,
|
|
575
|
+
},
|
|
518
576
|
)
|
|
519
577
|
existing_conscious_memories = cursor.fetchall()
|
|
520
578
|
|
|
521
579
|
if not existing_conscious_memories:
|
|
522
580
|
logger.debug(
|
|
523
|
-
"
|
|
581
|
+
"[CONSCIOUS] No conscious-info memories found for initialization"
|
|
524
582
|
)
|
|
525
583
|
return False
|
|
526
584
|
|
|
585
|
+
# Batch process memories for efficiency
|
|
586
|
+
logger.debug(
|
|
587
|
+
f"[CONSCIOUS] Processing {len(existing_conscious_memories)} conscious memories..."
|
|
588
|
+
)
|
|
527
589
|
copied_count = 0
|
|
528
590
|
for memory_row in existing_conscious_memories:
|
|
529
591
|
success = self._copy_memory_to_short_term_sync(memory_row)
|
|
@@ -532,12 +594,12 @@ class Memori:
|
|
|
532
594
|
|
|
533
595
|
if copied_count > 0:
|
|
534
596
|
logger.info(
|
|
535
|
-
f"
|
|
597
|
+
f"[CONSCIOUS] Initialized {copied_count} conscious memories to short-term storage"
|
|
536
598
|
)
|
|
537
599
|
return True
|
|
538
600
|
else:
|
|
539
601
|
logger.debug(
|
|
540
|
-
"
|
|
602
|
+
"[CONSCIOUS] No new conscious memories to initialize (all were duplicates)"
|
|
541
603
|
)
|
|
542
604
|
return False
|
|
543
605
|
|
|
@@ -564,26 +626,25 @@ class Memori:
|
|
|
564
626
|
from sqlalchemy import text
|
|
565
627
|
|
|
566
628
|
with self.db_manager._get_connection() as connection:
|
|
567
|
-
#
|
|
629
|
+
# Database-agnostic duplicate check with safer pattern matching
|
|
568
630
|
existing_check = connection.execute(
|
|
569
631
|
text(
|
|
570
632
|
"""SELECT COUNT(*) FROM short_term_memory
|
|
571
633
|
WHERE namespace = :namespace
|
|
572
|
-
AND
|
|
573
|
-
|
|
574
|
-
OR summary = :summary)"""
|
|
634
|
+
AND (memory_id = :exact_id
|
|
635
|
+
OR memory_id LIKE :conscious_pattern)"""
|
|
575
636
|
),
|
|
576
637
|
{
|
|
577
638
|
"namespace": self.namespace or "default",
|
|
578
|
-
"
|
|
579
|
-
"
|
|
639
|
+
"exact_id": memory_id,
|
|
640
|
+
"conscious_pattern": f"conscious_{memory_id}_%",
|
|
580
641
|
},
|
|
581
642
|
)
|
|
582
643
|
|
|
583
644
|
existing_count = existing_check.scalar()
|
|
584
645
|
if existing_count > 0:
|
|
585
646
|
logger.debug(
|
|
586
|
-
f"
|
|
647
|
+
f"[CONSCIOUS] Skipping duplicate memory {memory_id[:8]}... - already exists in short-term memory"
|
|
587
648
|
)
|
|
588
649
|
return False
|
|
589
650
|
|
|
@@ -1892,7 +1953,7 @@ class Memori:
|
|
|
1892
1953
|
|
|
1893
1954
|
# Debug logging for conversation recording
|
|
1894
1955
|
logger.info(
|
|
1895
|
-
f"Recording conversation - Input: '{user_input[:
|
|
1956
|
+
f"[MEMORY] Recording conversation - Input: '{user_input[:60]}...' | Model: {model} | Session: {self.session_id[:8]}..."
|
|
1896
1957
|
)
|
|
1897
1958
|
|
|
1898
1959
|
# Parse response
|
|
@@ -1915,29 +1976,31 @@ class Memori:
|
|
|
1915
1976
|
namespace=self.namespace,
|
|
1916
1977
|
metadata=metadata or {},
|
|
1917
1978
|
)
|
|
1918
|
-
logger.debug(
|
|
1919
|
-
f"Successfully stored chat history for conversation: {chat_id}"
|
|
1920
|
-
)
|
|
1979
|
+
logger.debug(f"[MEMORY] Chat history stored - ID: {chat_id[:8]}...")
|
|
1921
1980
|
|
|
1922
1981
|
# Always process into long-term memory when memory agent is available
|
|
1923
1982
|
if self.memory_agent:
|
|
1924
1983
|
self._schedule_memory_processing(
|
|
1925
1984
|
chat_id, user_input, response_text, response_model
|
|
1926
1985
|
)
|
|
1927
|
-
logger.debug(f"
|
|
1986
|
+
logger.debug(f"[MEMORY] Processing scheduled - ID: {chat_id[:8]}...")
|
|
1928
1987
|
else:
|
|
1929
1988
|
logger.warning(
|
|
1930
|
-
f"
|
|
1989
|
+
f"[MEMORY] Agent unavailable, skipping processing - ID: {chat_id[:8]}..."
|
|
1931
1990
|
)
|
|
1932
1991
|
|
|
1933
|
-
logger.info(
|
|
1992
|
+
logger.info(
|
|
1993
|
+
f"[MEMORY] Conversation recorded successfully - ID: {chat_id[:8]}..."
|
|
1994
|
+
)
|
|
1934
1995
|
return chat_id
|
|
1935
1996
|
|
|
1936
1997
|
except Exception as e:
|
|
1937
|
-
logger.error(
|
|
1998
|
+
logger.error(
|
|
1999
|
+
f"[MEMORY] Failed to record conversation {chat_id[:8]}... - {type(e).__name__}: {e}"
|
|
2000
|
+
)
|
|
1938
2001
|
import traceback
|
|
1939
2002
|
|
|
1940
|
-
logger.
|
|
2003
|
+
logger.debug(f"[MEMORY] Recording error details: {traceback.format_exc()}")
|
|
1941
2004
|
raise
|
|
1942
2005
|
|
|
1943
2006
|
def _schedule_memory_processing(
|
|
@@ -28,6 +28,21 @@ class DatabaseAutoCreator:
|
|
|
28
28
|
self.schema_init = schema_init
|
|
29
29
|
self.utils = DatabaseConnectionUtils()
|
|
30
30
|
|
|
31
|
+
def _is_gibsonai_temp_connection(self, components: dict[str, str] | None) -> bool:
|
|
32
|
+
"""Detect GibsonAI temporary database credentials to avoid noisy warnings."""
|
|
33
|
+
if not components:
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
host = (components.get("host") or "").lower()
|
|
37
|
+
if "gibsonai.com" not in host:
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
user = components.get("user") or components.get("username") or ""
|
|
41
|
+
database = components.get("database") or ""
|
|
42
|
+
|
|
43
|
+
# GibsonAI temporary credentials follow predictable us_/db_ prefixes
|
|
44
|
+
return user.startswith("us_") or database.startswith("db_")
|
|
45
|
+
|
|
31
46
|
def ensure_database_exists(self, connection_string: str) -> str:
|
|
32
47
|
"""
|
|
33
48
|
Ensure target database exists, creating it if necessary.
|
|
@@ -45,6 +60,7 @@ class DatabaseAutoCreator:
|
|
|
45
60
|
logger.debug("Auto-creation disabled, using original connection string")
|
|
46
61
|
return connection_string
|
|
47
62
|
|
|
63
|
+
components = None
|
|
48
64
|
try:
|
|
49
65
|
# Parse connection string
|
|
50
66
|
components = self.utils.parse_connection_string(connection_string)
|
|
@@ -56,6 +72,13 @@ class DatabaseAutoCreator:
|
|
|
56
72
|
)
|
|
57
73
|
return connection_string
|
|
58
74
|
|
|
75
|
+
# Skip noisy warnings for managed GibsonAI temporary databases
|
|
76
|
+
if self._is_gibsonai_temp_connection(components):
|
|
77
|
+
logger.debug(
|
|
78
|
+
"[DB_SETUP] GibsonAI managed database detected - skipping auto-creation checks"
|
|
79
|
+
)
|
|
80
|
+
return connection_string
|
|
81
|
+
|
|
59
82
|
# Validate database name
|
|
60
83
|
if not self.utils.validate_database_name(components["database"]):
|
|
61
84
|
raise ValueError(f"Invalid database name: {components['database']}")
|
|
@@ -70,10 +93,39 @@ class DatabaseAutoCreator:
|
|
|
70
93
|
logger.info(f"Successfully created database '{components['database']}'")
|
|
71
94
|
return connection_string
|
|
72
95
|
|
|
96
|
+
except PermissionError as e:
|
|
97
|
+
if components and self._is_gibsonai_temp_connection(components):
|
|
98
|
+
logger.debug(
|
|
99
|
+
"[DB_SETUP] GibsonAI managed database does not allow auto-creation (permission denied)"
|
|
100
|
+
)
|
|
101
|
+
return connection_string
|
|
102
|
+
|
|
103
|
+
logger.error(f"[DB_SETUP] Permission denied - {e}")
|
|
104
|
+
if components:
|
|
105
|
+
logger.warning(
|
|
106
|
+
f"[DB_SETUP] Database '{components['database']}' may need manual creation with proper permissions"
|
|
107
|
+
)
|
|
108
|
+
else:
|
|
109
|
+
logger.warning(
|
|
110
|
+
"[DB_SETUP] Database may need manual creation with proper permissions"
|
|
111
|
+
)
|
|
112
|
+
return connection_string
|
|
113
|
+
except RuntimeError as e:
|
|
114
|
+
logger.error(f"[DB_SETUP] Database creation error - {e}")
|
|
115
|
+
logger.info(
|
|
116
|
+
"[DB_SETUP] Proceeding with original connection string, database may need manual setup"
|
|
117
|
+
)
|
|
118
|
+
return connection_string
|
|
73
119
|
except Exception as e:
|
|
74
|
-
logger.error(
|
|
75
|
-
|
|
76
|
-
|
|
120
|
+
logger.error(
|
|
121
|
+
f"[DB_SETUP] Unexpected database auto-creation failure - {type(e).__name__}: {e}"
|
|
122
|
+
)
|
|
123
|
+
if components:
|
|
124
|
+
logger.debug(
|
|
125
|
+
f"[DB_SETUP] Connection string: {components['engine']}://{components['host']}:{components['port']}/{components['database']}"
|
|
126
|
+
)
|
|
127
|
+
else:
|
|
128
|
+
logger.debug(f"[DB_SETUP] Connection string: {connection_string}")
|
|
77
129
|
return connection_string
|
|
78
130
|
|
|
79
131
|
def _database_exists(self, components: dict[str, str]) -> bool:
|
|
@@ -90,7 +142,12 @@ class DatabaseAutoCreator:
|
|
|
90
142
|
return False
|
|
91
143
|
|
|
92
144
|
except Exception as e:
|
|
93
|
-
|
|
145
|
+
if self._is_gibsonai_temp_connection(components):
|
|
146
|
+
logger.debug(
|
|
147
|
+
"[DB_CONNECTION] Skipping GibsonAI database existence check due to restricted permissions"
|
|
148
|
+
)
|
|
149
|
+
else:
|
|
150
|
+
logger.error(f"Failed to check database existence: {e}")
|
|
94
151
|
return False
|
|
95
152
|
|
|
96
153
|
def _postgresql_database_exists(self, components: dict[str, str]) -> bool:
|
|
@@ -176,7 +233,17 @@ class DatabaseAutoCreator:
|
|
|
176
233
|
logger.error(error_msg)
|
|
177
234
|
return False
|
|
178
235
|
except Exception as e:
|
|
179
|
-
|
|
236
|
+
if self._is_gibsonai_temp_connection(components):
|
|
237
|
+
logger.debug(
|
|
238
|
+
f"[DB_CONNECTION] GibsonAI existence check bypassed for '{components['database']}' ({e})"
|
|
239
|
+
)
|
|
240
|
+
else:
|
|
241
|
+
logger.error(
|
|
242
|
+
f"[DB_CONNECTION] MySQL database existence check failed for '{components['database']}': {e}"
|
|
243
|
+
)
|
|
244
|
+
logger.debug(
|
|
245
|
+
f"[DB_CONNECTION] Connection details - host: {components.get('host')}, port: {components.get('port')}, user: {components.get('user') or components.get('username')}"
|
|
246
|
+
)
|
|
180
247
|
return False
|
|
181
248
|
|
|
182
249
|
def _create_database(self, components: dict[str, str]) -> None:
|