mdb-engine 0.7.3__py3-none-any.whl → 0.7.4__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.
mdb_engine/__init__.py CHANGED
@@ -81,7 +81,7 @@ from .repositories import Entity, MongoRepository, Repository, UnitOfWork
81
81
  # Utilities
82
82
  from .utils import clean_mongo_doc, clean_mongo_docs
83
83
 
84
- __version__ = "0.7.3"
84
+ __version__ = "0.7.4"
85
85
 
86
86
  __all__ = [
87
87
  # Core Engine
mdb_engine/cli/main.py CHANGED
@@ -15,7 +15,7 @@ from .commands.validate import validate
15
15
 
16
16
 
17
17
  @click.group()
18
- @click.version_option(version="0.7.3", prog_name="mdb")
18
+ @click.version_option(version="0.7.4", prog_name="mdb")
19
19
  def cli() -> None:
20
20
  """
21
21
  MDB_ENGINE CLI - Manifest management tool.
mdb_engine/core/engine.py CHANGED
@@ -910,13 +910,14 @@ class MongoDBEngine:
910
910
 
911
911
  def get_memory_service(self, slug: str) -> Any | None:
912
912
  """
913
- Get Mem0 memory service for an app.
913
+ Get memory service for an app (returns BaseMemoryService instance).
914
914
 
915
915
  Args:
916
916
  slug: App slug
917
917
 
918
918
  Returns:
919
- Mem0MemoryService instance if memory is enabled for this app, None otherwise
919
+ BaseMemoryService instance (currently Mem0MemoryService) if memory is enabled
920
+ for this app, None otherwise
920
921
 
921
922
  Example:
922
923
  ```python
@@ -63,10 +63,10 @@ class ServiceInitializer:
63
63
  self, slug: str, memory_config: dict[str, Any] | None
64
64
  ) -> None:
65
65
  """
66
- Initialize Mem0 memory service for an app.
66
+ Initialize memory service for an app (defaults to Mem0 implementation).
67
67
 
68
68
  Memory support is OPTIONAL - only processes if dependencies are available.
69
- mem0 handles embeddings and LLM via environment variables (.env).
69
+ Currently uses Mem0.ai which handles embeddings and LLM via environment variables (.env).
70
70
 
71
71
  Args:
72
72
  slug: App slug
@@ -356,13 +356,14 @@ class ServiceInitializer:
356
356
 
357
357
  def get_memory_service(self, slug: str) -> Any | None:
358
358
  """
359
- Get Mem0 memory service for an app.
359
+ Get memory service for an app (returns BaseMemoryService instance).
360
360
 
361
361
  Args:
362
362
  slug: App slug
363
363
 
364
364
  Returns:
365
- Mem0MemoryService instance if memory is enabled for this app, None otherwise
365
+ BaseMemoryService instance (currently Mem0MemoryService) if memory is enabled
366
+ for this app, None otherwise
366
367
  """
367
368
  try:
368
369
  service = self._memory_services.get(slug)
mdb_engine/core/types.py CHANGED
@@ -11,9 +11,9 @@ This module is part of MDB_ENGINE - MongoDB Engine.
11
11
  from typing import TYPE_CHECKING, Any, Literal, TypedDict
12
12
 
13
13
  if TYPE_CHECKING:
14
- from ..memory import Mem0MemoryService
14
+ from ..memory import BaseMemoryService
15
15
  else:
16
- Mem0MemoryService = Any
16
+ BaseMemoryService = Any
17
17
 
18
18
 
19
19
  # ============================================================================
@@ -33,7 +33,7 @@ if TYPE_CHECKING:
33
33
  from .core.engine import MongoDBEngine
34
34
  from .database.scoped_wrapper import ScopedMongoWrapper
35
35
  from .embeddings.service import EmbeddingService
36
- from .memory.service import Mem0MemoryService
36
+ from .memory import BaseMemoryService
37
37
 
38
38
  logger = logging.getLogger(__name__)
39
39
 
@@ -121,8 +121,8 @@ async def get_embedding_service(request: Request) -> "EmbeddingService":
121
121
  raise HTTPException(503, f"Failed to initialize embedding service: {e}") from e
122
122
 
123
123
 
124
- async def get_memory_service(request: Request) -> Optional["Mem0MemoryService"]:
125
- """Get the Mem0 memory service if configured."""
124
+ async def get_memory_service(request: Request) -> Optional["BaseMemoryService"]:
125
+ """Get the memory service if configured (defaults to Mem0 implementation)."""
126
126
  engine = getattr(request.app.state, "engine", None)
127
127
  slug = getattr(request.app.state, "app_slug", None)
128
128
  if not engine or not slug:
@@ -2,9 +2,37 @@
2
2
 
3
3
  Mem0.ai integration for intelligent memory management in MDB_ENGINE applications. Provides semantic memory storage, retrieval, and inference capabilities with MongoDB integration.
4
4
 
5
+ ## 🎉 What's New
6
+
7
+ ### Extensible Architecture (Latest)
8
+
9
+ **Base Class Pattern for Future Extensibility!**
10
+
11
+ The memory service now uses an abstract base class pattern, enabling future memory provider implementations while maintaining backward compatibility:
12
+
13
+ - **🏗️ BaseMemoryService**: Abstract base class defining the memory service interface
14
+ - **🔌 Provider Extensibility**: Easy to add new memory providers (LangChain, custom implementations, etc.)
15
+ - **✅ Backward Compatible**: All existing code continues to work without changes
16
+ - **📝 Type Safety**: Better IDE support and type checking with abstract base class
17
+ - **🎯 Consistent API**: All memory providers implement the same interface
18
+
19
+ ### v0.7.4 Enhancements
20
+
21
+ **Enhanced Mem0 Integration - Production Ready!**
22
+
23
+ - **🔧 Hybrid Update Pattern**: Content updates via Mem0 (triggers re-embedding), metadata updates via direct MongoDB (full control, no API limitations)
24
+ - **📊 Direct MongoDB Access**: Reliable data retrieval directly from MongoDB, bypassing Mem0 API inconsistencies
25
+ - **🏷️ Full Metadata Support**: Update any metadata field without restrictions - not limited by Mem0's API
26
+ - **✅ Correct Mem0 Structure**: Properly handles Mem0's MongoDB structure (`_id` as document ID, `payload` for memory data)
27
+ - **🛡️ Robust Error Handling**: Specific exception handling with proper KeyboardInterrupt/SystemExit propagation
28
+ - **🔍 Reliable Returns**: Always returns normalized documents fetched directly from MongoDB (guaranteed structure)
29
+
30
+ > 📖 **Want to understand why we use manual MongoDB access?** See the [Mem0 Implementation Guide](../../docs/guides/MEM0_IMPLEMENTATION.md) for detailed explanations of our architectural decisions, Mem0's MongoDB structure, and things to watch out for.
31
+
5
32
  ## Features
6
33
 
7
- - **Mem0 Integration**: Wrapper around Mem0.ai for intelligent memory management
34
+ - **Extensible Architecture**: Base class pattern allows for multiple memory provider implementations
35
+ - **Mem0 Integration**: Default implementation using Mem0.ai for intelligent memory management
8
36
  - **MongoDB Storage**: Built-in MongoDB vector store integration
9
37
  - **Auto-Detection**: Automatically detects OpenAI or Azure OpenAI from environment variables
10
38
  - **Semantic Search**: Vector-based semantic memory search
@@ -67,7 +95,7 @@ Enable memory service in your `manifest.json`:
67
95
  ### Basic Usage
68
96
 
69
97
  ```python
70
- from mdb_engine.memory import Mem0MemoryService
98
+ from mdb_engine.memory import BaseMemoryService # Base class for type hints
71
99
  from mdb_engine.core import MongoDBEngine
72
100
 
73
101
  # Initialize engine
@@ -75,7 +103,8 @@ engine = MongoDBEngine(mongo_uri="...", db_name="...")
75
103
  await engine.initialize()
76
104
 
77
105
  # Get memory service (automatically configured from manifest)
78
- memory_service = engine.get_memory_service("my_app")
106
+ # Returns BaseMemoryService instance (currently Mem0MemoryService)
107
+ memory_service: BaseMemoryService = engine.get_memory_service("my_app")
79
108
 
80
109
  # Add memory
81
110
  memory = await memory_service.add(
@@ -163,14 +192,17 @@ results = await memory_service.search(
163
192
 
164
193
  ### Get Memories
165
194
 
166
- Retrieve memories for a user:
195
+ Retrieve memories for a user. The service automatically normalizes Mem0's MongoDB structure (`_id`, `payload`) into a consistent API format:
167
196
 
168
197
  ```python
169
198
  # Get all memories
170
199
  all_memories = await memory_service.get_all(user_id="user123")
200
+ # Returns normalized format: [{"id": "...", "memory": "...", "metadata": {...}, ...}]
171
201
 
172
202
  # Get specific memory
173
203
  memory = await memory_service.get(memory_id="memory_123", user_id="user123")
204
+ # Returns normalized format: {"id": "...", "memory": "...", "metadata": {...}, ...}
205
+ # Note: memory_id can be either Mem0's _id or the normalized id field
174
206
 
175
207
  # Get memories with filters
176
208
  memories = await memory_service.get_all(
@@ -179,9 +211,16 @@ memories = await memory_service.get_all(
179
211
  )
180
212
  ```
181
213
 
214
+ **Note**: The service handles Mem0's internal MongoDB structure (`_id` as document ID, `payload` containing memory data) automatically. All methods return normalized documents with consistent `id`, `memory`, `text`, and `metadata` fields.
215
+
182
216
  ### Update Memory
183
217
 
184
- Update existing memories in-place while preserving the original memory ID and creation timestamp:
218
+ Update existing memories using a **hybrid approach** that combines Mem0's embedding capabilities with direct MongoDB control:
219
+
220
+ **Architecture:**
221
+ - **Content Updates**: Routed via Mem0 (triggers automatic re-embedding)
222
+ - **Metadata Updates**: Routed via direct PyMongo (full control, no API limitations)
223
+ - **Return Value**: Always fetched from MongoDB (guaranteed correct structure)
185
224
 
186
225
  ```python
187
226
  # Update memory content and metadata
@@ -200,14 +239,21 @@ updated = memory_service.update(
200
239
  metadata={"updated": True}
201
240
  )
202
241
 
203
- # Update only metadata (content unchanged)
242
+ # Update only metadata (content unchanged) - FULLY SUPPORTED
243
+ updated = memory_service.update(
244
+ memory_id="memory_123",
245
+ user_id="user123",
246
+ metadata={"category": "updated", "priority": "high"}
247
+ )
248
+
249
+ # Update only content (no metadata changes)
204
250
  updated = memory_service.update(
205
251
  memory_id="memory_123",
206
252
  user_id="user123",
207
- metadata={"category": "updated"}
253
+ memory="Updated content only"
208
254
  )
209
255
 
210
- # Backward compatibility: using 'data' parameter
256
+ # Using 'data' parameter
211
257
  updated = memory_service.update(
212
258
  memory_id="memory_123",
213
259
  user_id="user123",
@@ -217,12 +263,18 @@ updated = memory_service.update(
217
263
  ```
218
264
 
219
265
  **Key Features:**
266
+ - **Hybrid Architecture**: Mem0 handles embeddings, MongoDB handles data persistence
267
+ - **Full Metadata Support**: Update any metadata field (not limited by Mem0 API)
220
268
  - **Preserves Memory ID**: The original memory ID is maintained
221
269
  - **Preserves Creation Timestamp**: `created_at` is not modified
222
270
  - **Updates Timestamp**: `updated_at` is automatically set to current time
223
- - **Recomputes Embeddings**: If content changes, the embedding vector is automatically recomputed
224
- - **Metadata Merging**: New metadata is merged with existing metadata (doesn't replace)
271
+ - **Recomputes Embeddings**: If content changes, the embedding vector is automatically recomputed via Mem0
272
+ - **Reliable Returns**: Always returns the actual document from MongoDB (not Mem0's response format)
225
273
  - **Partial Updates**: Can update content only, metadata only, or both
274
+ - **Security**: Validates user_id ownership before allowing updates
275
+ - **Mem0 Structure Aware**: Correctly handles Mem0's MongoDB structure (`_id` as document ID, `payload` for memory data)
276
+ - **Direct MongoDB Access**: Uses PyMongo for reliable data operations, ensuring consistency
277
+ - **Normalized Responses**: All methods return consistent document structure regardless of Mem0's internal format
226
278
 
227
279
  ### Delete Memory
228
280
 
@@ -336,25 +388,93 @@ for memory in insights:
336
388
  print(f"Insights: {memory.get('insights')}")
337
389
  ```
338
390
 
391
+ ## Architecture
392
+
393
+ ### Base Class Pattern
394
+
395
+ The memory service uses an abstract base class pattern for extensibility:
396
+
397
+ ```python
398
+ from mdb_engine.memory import BaseMemoryService, MemoryServiceError
399
+
400
+ # BaseMemoryService defines the interface
401
+ # Mem0MemoryService implements it (default provider)
402
+ # Future providers can inherit from BaseMemoryService
403
+ ```
404
+
405
+ **Benefits:**
406
+ - **Type Safety**: Use `BaseMemoryService` for type hints
407
+ - **Extensibility**: Easy to add new providers (LangChain, custom, etc.)
408
+ - **Consistency**: All providers implement the same interface
409
+ - **Backward Compatible**: Existing code works without changes
410
+
411
+ ### Creating Custom Memory Providers
412
+
413
+ To create a custom memory provider, inherit from `BaseMemoryService`:
414
+
415
+ ```python
416
+ from mdb_engine.memory import BaseMemoryService, MemoryServiceError
417
+
418
+ class CustomMemoryService(BaseMemoryService):
419
+ """Custom memory service implementation."""
420
+
421
+ def __init__(self, mongo_uri: str, db_name: str, app_slug: str, config: dict | None = None):
422
+ # Initialize your custom implementation
423
+ pass
424
+
425
+ def add(self, messages, user_id=None, metadata=None, **kwargs):
426
+ # Implement add method
427
+ pass
428
+
429
+ # Implement all other abstract methods...
430
+ def get_all(self, user_id=None, limit=100, filters=None, **kwargs):
431
+ pass
432
+
433
+ def search(self, query, user_id=None, limit=5, filters=None, **kwargs):
434
+ pass
435
+
436
+ def get(self, memory_id, user_id=None, **kwargs):
437
+ pass
438
+
439
+ def delete(self, memory_id, user_id=None, **kwargs):
440
+ pass
441
+
442
+ def delete_all(self, user_id=None, **kwargs):
443
+ pass
444
+
445
+ def update(self, memory_id, user_id=None, memory=None, metadata=None, **kwargs):
446
+ pass
447
+ ```
448
+
339
449
  ## API Reference
340
450
 
451
+ ### BaseMemoryService
452
+
453
+ Abstract base class for all memory service implementations. Defines the standard interface.
454
+
341
455
  ### Mem0MemoryService
342
456
 
457
+ Default implementation using Mem0.ai. Inherits from `BaseMemoryService`.
458
+
343
459
  #### Initialization
344
460
 
345
461
  ```python
346
462
  Mem0MemoryService(
347
463
  mongo_uri: str,
348
464
  db_name: str,
349
- collection_name: str = "memories",
350
- app_slug: str = None,
351
- embedding_model: str = "text-embedding-3-small",
352
- embedding_dimensions: int = None,
353
- chat_model: str = "gpt-4",
354
- temperature: float = 0.7,
355
- infer: bool = True,
356
- enable_graph: bool = False,
357
- config: dict = None
465
+ app_slug: str,
466
+ config: dict = None # Optional configuration
467
+ )
468
+
469
+ # Or use the factory function
470
+ from mdb_engine.memory import get_memory_service
471
+
472
+ memory_service = get_memory_service(
473
+ mongo_uri="...",
474
+ db_name="...",
475
+ app_slug="...",
476
+ config={...},
477
+ provider="mem0" # Default, future providers can be specified here
358
478
  )
359
479
  ```
360
480
 
@@ -513,15 +633,19 @@ knowledge = await memory_service.search(
513
633
  ## Error Handling
514
634
 
515
635
  ```python
516
- from mdb_engine.memory import Mem0MemoryServiceError
636
+ from mdb_engine.memory import MemoryServiceError, Mem0MemoryServiceError
517
637
 
518
638
  try:
519
639
  memory = await memory_service.add(
520
640
  messages=[{"role": "user", "content": "Test"}],
521
641
  user_id="user123"
522
642
  )
523
- except Mem0MemoryServiceError as e:
643
+ except MemoryServiceError as e:
644
+ # Base exception for all memory service errors
524
645
  print(f"Memory service error: {e}")
646
+ except Mem0MemoryServiceError as e:
647
+ # Specific exception for Mem0 implementation
648
+ print(f"Mem0 memory service error: {e}")
525
649
  except (ValueError, TypeError, ConnectionError) as e:
526
650
  print(f"Configuration or connection error: {e}")
527
651
  ```
@@ -14,16 +14,25 @@ Key Features:
14
14
  - **Optional LLM Inference**: Can leverage LLM service for automatic memory
15
15
  extraction (set infer: false to disable)
16
16
  - **Graph Support**: Optional knowledge graph construction for entity relationships
17
+ - **Extensible Architecture**: Base class allows for future memory provider implementations
17
18
 
18
19
  Dependencies:
19
20
  pip install mem0ai
20
21
  """
21
22
 
23
+ # Import base classes
24
+ from .base import BaseMemoryService, MemoryServiceError
25
+
22
26
  # Import service components (mem0 import is lazy within service.py)
23
27
  from .service import Mem0MemoryService, Mem0MemoryServiceError, get_memory_service
24
28
 
25
29
  __all__ = [
30
+ # Base classes (for extensibility)
31
+ "BaseMemoryService",
32
+ "MemoryServiceError",
33
+ # Mem0 implementation (default)
26
34
  "Mem0MemoryService",
27
35
  "Mem0MemoryServiceError",
36
+ # Factory function
28
37
  "get_memory_service",
29
38
  ]
@@ -0,0 +1,194 @@
1
+ """
2
+ Base Memory Service Interface
3
+
4
+ Abstract base class for memory service implementations.
5
+ This allows for extensibility with different memory providers (Mem0, LangChain, custom, etc.)
6
+ while maintaining a consistent API.
7
+
8
+ This module is part of MDB_ENGINE - MongoDB Engine.
9
+ """
10
+
11
+ from abc import ABC, abstractmethod
12
+ from typing import Any
13
+
14
+
15
+ class MemoryServiceError(Exception):
16
+ """Base exception for all memory service errors."""
17
+
18
+ pass
19
+
20
+
21
+ class BaseMemoryService(ABC):
22
+ """
23
+ Abstract base class for memory service implementations.
24
+
25
+ This class defines the interface that all memory service implementations must follow.
26
+ Concrete implementations (e.g., Mem0MemoryService) inherit from this class and
27
+ implement the abstract methods.
28
+
29
+ All memory operations are scoped per user_id for safety and data isolation.
30
+ """
31
+
32
+ @abstractmethod
33
+ def add(
34
+ self,
35
+ messages: str | list[dict[str, str]],
36
+ user_id: str | None = None,
37
+ metadata: dict[str, Any] | None = None,
38
+ bucket_id: str | None = None,
39
+ bucket_type: str | None = None,
40
+ raw_content: str | None = None,
41
+ **kwargs,
42
+ ) -> list[dict[str, Any]]:
43
+ """
44
+ Add memories with user scoping and metadata convenience.
45
+
46
+ Args:
47
+ messages: Memory content as a string or list of message dicts
48
+ user_id: User ID for scoping (optional but recommended)
49
+ metadata: Additional metadata to store with the memory
50
+ bucket_id: Bucket ID for organizing memories
51
+ bucket_type: Type of bucket (e.g., "general", "file", "conversation")
52
+ raw_content: Raw content to store alongside extracted facts
53
+ **kwargs: Additional provider-specific arguments
54
+
55
+ Returns:
56
+ List of created memory objects with their IDs and metadata
57
+ """
58
+ pass
59
+
60
+ @abstractmethod
61
+ def get_all(
62
+ self,
63
+ user_id: str | None = None,
64
+ limit: int = 100,
65
+ filters: dict[str, Any] | None = None,
66
+ **kwargs,
67
+ ) -> list[dict[str, Any]]:
68
+ """
69
+ Get all memories with optional filtering.
70
+
71
+ Args:
72
+ user_id: User ID to filter memories (optional)
73
+ limit: Maximum number of memories to return
74
+ filters: Additional filters to apply (provider-specific)
75
+ **kwargs: Additional provider-specific arguments
76
+
77
+ Returns:
78
+ List of memory objects
79
+ """
80
+ pass
81
+
82
+ @abstractmethod
83
+ def search(
84
+ self,
85
+ query: str,
86
+ user_id: str | None = None,
87
+ limit: int = 5,
88
+ filters: dict[str, Any] | None = None,
89
+ **kwargs,
90
+ ) -> list[dict[str, Any]]:
91
+ """
92
+ Perform semantic search across memories.
93
+
94
+ Args:
95
+ query: Search query string
96
+ user_id: User ID to scope search (optional)
97
+ limit: Maximum number of results to return
98
+ filters: Additional metadata filters to apply
99
+ **kwargs: Additional provider-specific arguments
100
+
101
+ Returns:
102
+ List of memory objects matching the query, ordered by relevance
103
+ """
104
+ pass
105
+
106
+ @abstractmethod
107
+ def get(
108
+ self,
109
+ memory_id: str,
110
+ user_id: str | None = None,
111
+ **kwargs,
112
+ ) -> dict[str, Any] | None:
113
+ """
114
+ Get a single memory by ID.
115
+
116
+ Args:
117
+ memory_id: Unique identifier for the memory
118
+ user_id: User ID for security scoping (optional)
119
+ **kwargs: Additional provider-specific arguments
120
+
121
+ Returns:
122
+ Memory object if found, None otherwise
123
+ """
124
+ pass
125
+
126
+ @abstractmethod
127
+ def delete(
128
+ self,
129
+ memory_id: str,
130
+ user_id: str | None = None,
131
+ **kwargs,
132
+ ) -> bool:
133
+ """
134
+ Delete a single memory by ID.
135
+
136
+ Args:
137
+ memory_id: Unique identifier for the memory to delete
138
+ user_id: User ID for security scoping (optional)
139
+ **kwargs: Additional provider-specific arguments
140
+
141
+ Returns:
142
+ True if deletion was successful, False otherwise
143
+ """
144
+ pass
145
+
146
+ @abstractmethod
147
+ def delete_all(
148
+ self,
149
+ user_id: str | None = None,
150
+ **kwargs,
151
+ ) -> bool:
152
+ """
153
+ Delete all memories for a user.
154
+
155
+ Args:
156
+ user_id: User ID whose memories should be deleted (optional)
157
+ **kwargs: Additional provider-specific arguments
158
+
159
+ Returns:
160
+ True if deletion was successful, False otherwise
161
+ """
162
+ pass
163
+
164
+ @abstractmethod
165
+ def update(
166
+ self,
167
+ memory_id: str,
168
+ user_id: str | None = None,
169
+ memory: str | None = None,
170
+ data: str | dict[str, Any] | None = None,
171
+ messages: str | list[dict[str, str]] | None = None,
172
+ metadata: dict[str, Any] | None = None,
173
+ **kwargs,
174
+ ) -> dict[str, Any] | None:
175
+ """
176
+ Update an existing memory's content and/or metadata.
177
+
178
+ Args:
179
+ memory_id: Unique identifier for the memory to update (required)
180
+ user_id: User ID for security scoping (optional)
181
+ memory: New memory content as a string (optional)
182
+ data: Alternative parameter for content (string or dict) (optional)
183
+ messages: Alternative way to provide content as messages (optional)
184
+ metadata: Metadata updates (optional)
185
+ **kwargs: Additional provider-specific arguments
186
+
187
+ Returns:
188
+ Updated memory object if successful, None if memory not found
189
+
190
+ Raises:
191
+ MemoryServiceError: If update operation fails
192
+ ValueError: If memory_id is invalid or empty
193
+ """
194
+ pass
@@ -1,13 +1,37 @@
1
1
  """
2
2
  Mem0 Memory Service Implementation
3
3
  Production-ready wrapper for Mem0.ai with strict metadata schema for MongoDB.
4
+
5
+ v0.7.4: Enhanced with hybrid update pattern and direct MongoDB access for reliable
6
+ memory operations. Properly handles Mem0's MongoDB structure (_id, payload).
4
7
  """
5
8
 
6
9
  import logging
7
10
  import os
8
11
  import tempfile
12
+ from datetime import datetime
9
13
  from typing import Any
10
14
 
15
+ from .base import BaseMemoryService, MemoryServiceError
16
+
17
+ # Required: Direct PyMongo access
18
+ try:
19
+ from pymongo import MongoClient
20
+ from pymongo.errors import (
21
+ ConfigurationError,
22
+ ConnectionFailure,
23
+ InvalidURI,
24
+ PyMongoError,
25
+ ServerSelectionTimeoutError,
26
+ )
27
+ except ImportError:
28
+ MongoClient = None
29
+ ConnectionFailure = None
30
+ ConfigurationError = None
31
+ ServerSelectionTimeoutError = None
32
+ InvalidURI = None
33
+ PyMongoError = None
34
+
11
35
  # Set MEM0_DIR environment variable early to avoid permission issues
12
36
  if "MEM0_DIR" not in os.environ:
13
37
  mem0_dir = os.path.join(tempfile.gettempdir(), ".mem0")
@@ -39,23 +63,29 @@ def _check_mem0_available():
39
63
  logger = logging.getLogger(__name__)
40
64
 
41
65
 
42
- class Mem0MemoryServiceError(Exception):
66
+ class Mem0MemoryServiceError(MemoryServiceError):
67
+ """Exception raised by Mem0MemoryService operations."""
68
+
43
69
  pass
44
70
 
45
71
 
46
- class Mem0MemoryService:
72
+ class Mem0MemoryService(BaseMemoryService):
47
73
  """
48
74
  Production-ready Mem0 Memory Service with MongoDB integration.
49
75
 
50
76
  Features:
77
+ - Hybrid update pattern: Mem0 for embeddings, MongoDB for data persistence
78
+ - Full metadata support via direct MongoDB access
51
79
  - In-place memory updates preserving IDs and timestamps
52
80
  - Automatic embedding recomputation on content changes
53
81
  - Knowledge graph support (if enabled in Mem0 config)
54
82
  - Comprehensive error handling and logging
55
- - Backward compatibility with existing code
83
+ - Reliable return values fetched directly from MongoDB
56
84
 
57
- All operations go through Mem0's API to ensure proper state management,
58
- graph updates, and relationship handling.
85
+ Update Architecture:
86
+ - Content updates routed via Mem0 (triggers re-embedding)
87
+ - Metadata updates routed via direct PyMongo (full control)
88
+ - Final result always fetched from MongoDB (guaranteed structure)
59
89
  """
60
90
 
61
91
  def __init__(
@@ -77,6 +107,26 @@ class Mem0MemoryService:
77
107
  self.collection_name = (config or {}).get("collection_name", f"{app_slug}_memories")
78
108
  self.infer = (config or {}).get("infer", True)
79
109
 
110
+ # ---------------------------------------------------------
111
+ # 1. SETUP DIRECT MONGODB ACCESS (The "Backdoor")
112
+ # ---------------------------------------------------------
113
+ if MongoClient is None:
114
+ raise Mem0MemoryServiceError("pymongo is required. pip install pymongo")
115
+
116
+ try:
117
+ self._client = MongoClient(mongo_uri)
118
+ self._db = self._client[db_name]
119
+ self.memories_collection = self._db[self.collection_name]
120
+ logger.info(f"✅ Direct MongoDB connection established for {self.collection_name}")
121
+ except BaseException as e:
122
+ # MongoDB connection may raise various exceptions. We catch BaseException
123
+ # (not Exception) to ensure we always raise Mem0MemoryServiceError for
124
+ # consistent error handling, but we re-raise KeyboardInterrupt and SystemExit
125
+ # to allow proper shutdown.
126
+ if isinstance(e, KeyboardInterrupt | SystemExit):
127
+ raise
128
+ raise Mem0MemoryServiceError(f"Failed to connect to MongoDB directly: {e}") from e
129
+
80
130
  # Ensure GOOGLE_API_KEY is set for mem0 compatibility
81
131
  # (mem0 expects GOOGLE_API_KEY, not GEMINI_API_KEY)
82
132
  # This ensures we use the DIRECT Gemini API
@@ -437,8 +487,50 @@ class Mem0MemoryService:
437
487
  return []
438
488
 
439
489
  def get(self, memory_id: str, user_id: str | None = None, **kwargs) -> dict[str, Any]:
490
+ """
491
+ Get memory by ID using direct MongoDB access for reliability.
492
+
493
+ Mem0 stores memories with _id as the MongoDB document ID.
494
+ Memory content and metadata are stored in the 'payload' field.
495
+ """
440
496
  try:
441
- return self.memory.get(memory_id, **kwargs)
497
+ # Mem0 uses _id as the MongoDB document ID
498
+ doc = self.memories_collection.find_one({"_id": memory_id})
499
+ if doc:
500
+ # Extract payload (where Mem0 stores the actual memory data)
501
+ payload = doc.get("payload", {})
502
+
503
+ # Build normalized memory document
504
+ memory_doc = {
505
+ "id": str(doc["_id"]), # Convert _id to id for API consistency
506
+ "memory": payload.get("memory") or payload.get("text"),
507
+ "text": payload.get("text") or payload.get("memory"),
508
+ "metadata": payload.get("metadata", {}),
509
+ "user_id": payload.get("user_id") or payload.get("metadata", {}).get("user_id"),
510
+ "created_at": payload.get("created_at"),
511
+ "updated_at": payload.get("updated_at"),
512
+ }
513
+
514
+ # Add any other payload fields
515
+ for key, value in payload.items():
516
+ if key not in [
517
+ "memory",
518
+ "text",
519
+ "metadata",
520
+ "user_id",
521
+ "created_at",
522
+ "updated_at",
523
+ ]:
524
+ memory_doc[key] = value
525
+
526
+ # Optional: Filter by user_id if provided
527
+ if user_id:
528
+ doc_user_id = memory_doc.get("user_id")
529
+ if doc_user_id and str(doc_user_id) != str(user_id):
530
+ return None
531
+
532
+ return memory_doc
533
+ return None
442
534
  except (
443
535
  ValueError,
444
536
  TypeError,
@@ -448,7 +540,19 @@ class Mem0MemoryService:
448
540
  RuntimeError,
449
541
  KeyError,
450
542
  ):
451
- return None
543
+ # Fallback to Mem0 if direct access fails
544
+ try:
545
+ return self.memory.get(memory_id, **kwargs)
546
+ except (
547
+ ValueError,
548
+ TypeError,
549
+ ConnectionError,
550
+ OSError,
551
+ AttributeError,
552
+ RuntimeError,
553
+ KeyError,
554
+ ):
555
+ return None
452
556
 
453
557
  def delete(self, memory_id: str, user_id: str | None = None, **kwargs) -> bool:
454
558
  try:
@@ -491,15 +595,27 @@ class Mem0MemoryService:
491
595
  **kwargs,
492
596
  ) -> dict[str, Any] | None:
493
597
  """
494
- Update an existing memory in-place with production-grade error handling.
598
+ Robust Hybrid Update Pattern:
599
+
600
+ This method uses a hybrid approach that combines Mem0's embedding capabilities
601
+ with direct MongoDB control for maximum flexibility and reliability.
602
+
603
+ **Architecture:**
604
+ 1. **Content Updates** → Routed via Mem0 (triggers automatic re-embedding)
605
+ 2. **Metadata Updates** → Routed via direct PyMongo (full control, no API limitations)
606
+ 3. **Return Value** → Always fetched from MongoDB (guaranteed correct structure)
607
+
608
+ **Why Hybrid?**
609
+ - Mem0's update() API doesn't support metadata parameter
610
+ - Mem0's return values can be inconsistent (dict, list, or status messages)
611
+ - Direct MongoDB access gives us full control over data persistence
612
+ - We use Mem0 purely as an "embedding utility" for content changes
495
613
 
496
614
  Updates the memory content and/or metadata while preserving:
497
615
  - Original memory ID (never changes)
498
616
  - Creation timestamp (created_at) - preserved
499
617
  - Other existing fields - preserved unless explicitly updated
500
618
 
501
- If content is updated, the embedding vector is automatically recomputed.
502
-
503
619
  Args:
504
620
  memory_id: The ID of the memory to update (required)
505
621
  user_id: The user ID who owns the memory (for scoping and security)
@@ -508,12 +624,13 @@ class Mem0MemoryService:
508
624
  Can be a string or dict with 'memory'/'text'/'content' key.
509
625
  messages: Alternative way to provide content as messages (optional).
510
626
  Can be a string or list of dicts with 'content' key.
511
- metadata: Metadata updates (optional, but NOT SUPPORTED by Mem0 update API).
512
- This parameter is accepted for API consistency but will be ignored.
627
+ metadata: Metadata updates (FULLY SUPPORTED via direct MongoDB).
628
+ Can update any metadata field, not limited by Mem0 API.
513
629
  **kwargs: Additional arguments passed to Mem0 operations
514
630
 
515
631
  Returns:
516
- Updated memory object with same ID, or None if memory not found
632
+ Updated memory object with same ID, fetched directly from MongoDB,
633
+ or None if memory not found
517
634
 
518
635
  Raises:
519
636
  Mem0MemoryServiceError: If update operation fails
@@ -521,7 +638,7 @@ class Mem0MemoryService:
521
638
 
522
639
  Example:
523
640
  ```python
524
- # Update content and metadata
641
+ # Update content and metadata (hybrid approach)
525
642
  updated = memory_service.update(
526
643
  memory_id="04f78986-dfad-46fe-8381-034bbee9a2fc",
527
644
  user_id="user123",
@@ -529,78 +646,164 @@ class Mem0MemoryService:
529
646
  metadata={"category": "technical", "updated": True}
530
647
  )
531
648
 
532
- # Update only metadata (content unchanged)
649
+ # Update only metadata (content unchanged) - FULLY SUPPORTED
533
650
  updated = memory_service.update(
534
651
  memory_id="04f78986-dfad-46fe-8381-034bbee9a2fc",
535
652
  user_id="user123",
536
- metadata={"category": "updated"}
653
+ metadata={"category": "updated", "priority": "high"}
654
+ )
655
+
656
+ # Update only content (no metadata)
657
+ updated = memory_service.update(
658
+ memory_id="04f78986-dfad-46fe-8381-034bbee9a2fc",
659
+ user_id="user123",
660
+ memory="Updated content only"
537
661
  )
538
662
  ```
539
663
  """
540
664
  if not memory_id or not isinstance(memory_id, str) or not memory_id.strip():
541
665
  raise ValueError("memory_id is required and must be a non-empty string")
542
666
 
543
- try:
544
- # Normalize data parameter (alternative to memory parameter)
545
- normalized_memory = self._normalize_content_input(memory, data, messages)
546
- normalized_metadata = self._normalize_metadata_input(metadata, data)
667
+ # 1. Normalize Inputs
668
+ normalized_memory = self._normalize_content_input(memory, data, messages)
669
+ normalized_metadata = self._normalize_metadata_input(metadata, data)
547
670
 
548
- existing_memory = self.get(memory_id=memory_id, user_id=user_id, **kwargs)
549
- if not existing_memory:
550
- logger.warning(
551
- f"Memory {memory_id} not found for update",
552
- extra={"memory_id": memory_id, "user_id": user_id},
553
- )
671
+ # 2. Check Existence (Fast check via ID)
672
+ # Mem0 uses _id as the MongoDB document ID
673
+ existing = self.memories_collection.find_one(
674
+ {"_id": memory_id}, {"_id": 1, "payload.user_id": 1, "payload.metadata.user_id": 1}
675
+ )
676
+ if not existing:
677
+ logger.warning(f"Memory {memory_id} not found.")
678
+ return None
679
+
680
+ # Optional: Security Scope Check
681
+ # Check user_id in payload (Mem0 stores it there)
682
+ if user_id:
683
+ payload = existing.get("payload", {})
684
+ existing_user_id = payload.get("user_id") or payload.get("metadata", {}).get("user_id")
685
+ if existing_user_id and str(existing_user_id) != str(user_id):
686
+ logger.warning(f"Unauthorized update attempt for {memory_id} by {user_id}")
554
687
  return None
555
688
 
556
- # Use Mem0's built-in update method
557
- # Mem0's Memory class update method handles:
558
- # - In-place updates preserving memory ID
559
- # - Automatic embedding recomputation
560
- # - Metadata merging
561
- # - User scoping
562
- # - Knowledge graph updates (if enabled)
563
- # - Relationship management
564
- if not hasattr(self.memory, "update") or not callable(self.memory.update):
565
- raise Mem0MemoryServiceError(
566
- "Mem0 update method not available. "
567
- "Please ensure you're using a compatible version of mem0ai "
568
- "that supports updates. Install with: pip install --upgrade mem0ai"
569
- )
689
+ # Use _id directly (Mem0's format)
690
+ actual_id = memory_id
570
691
 
571
- result = self._update_via_mem0(
572
- memory_id=memory_id,
573
- user_id=user_id,
574
- memory=normalized_memory,
575
- metadata=normalized_metadata,
576
- **kwargs,
577
- )
692
+ try:
693
+ # -------------------------------------------------
694
+ # STEP A: Content Update (Via Mem0 for Vectors)
695
+ # -------------------------------------------------
696
+ if normalized_memory:
697
+ logger.info(f"📝 Updating content for {actual_id} (triggering re-embedding)")
698
+ # We use Mem0 here specifically because it handles the embedding logic.
699
+ # We do NOT care what it returns.
700
+ try:
701
+ # Use the actual_id (which Mem0 recognizes)
702
+ self.memory.update(memory_id=actual_id, data=normalized_memory)
703
+ except BaseException as e:
704
+ # Mem0 is a third-party library that may raise any exception.
705
+ # We catch BaseException (not Exception) to ensure we always raise
706
+ # Mem0MemoryServiceError for consistent error handling, but we
707
+ # re-raise KeyboardInterrupt and SystemExit to allow proper shutdown.
708
+ if isinstance(e, KeyboardInterrupt | SystemExit):
709
+ raise
710
+ # If Mem0 fails (e.g. LLM rate limit, API error), we should abort
711
+ # or fall back to just text update without vector (risky for search).
712
+ logger.exception(f"Mem0 embedding update failed: {e}")
713
+ raise Mem0MemoryServiceError(f"Content update failed: {e}") from e
714
+
715
+ # -------------------------------------------------
716
+ # STEP B: Metadata Update (Direct PyMongo)
717
+ # -------------------------------------------------
718
+ if normalized_metadata:
719
+ logger.info(f"🏷️ Updating metadata for {actual_id}")
720
+
721
+ # Ensure user_id is in metadata for consistency
722
+ if user_id:
723
+ normalized_metadata["user_id"] = str(user_id)
724
+
725
+ # Mem0 stores everything in the 'payload' field
726
+ # We need to update payload.metadata and payload.user_id
727
+ update_fields = {}
728
+
729
+ # Handle metadata updates (nested under payload.metadata)
730
+ for k, v in normalized_metadata.items():
731
+ if k == "user_id":
732
+ # user_id can be at payload.user_id or payload.metadata.user_id
733
+ update_fields["payload.user_id"] = v
734
+ update_fields["payload.metadata.user_id"] = v
735
+ else:
736
+ update_fields[f"payload.metadata.{k}"] = v
737
+
738
+ # Add timestamp to payload
739
+ update_fields["payload.updated_at"] = datetime.utcnow().isoformat()
740
+
741
+ # Execute Atomic Update - Mem0 uses _id
742
+ self.memories_collection.update_one({"_id": actual_id}, {"$set": update_fields})
743
+
744
+ # -------------------------------------------------
745
+ # STEP C: Return The Truth (Direct DB Fetch)
746
+ # -------------------------------------------------
747
+ # We completely ignore Mem0's return value (which might be {"message": "ok"})
748
+ # and fetch the actual document from the database.
749
+ final_doc_raw = self.memories_collection.find_one({"_id": actual_id})
750
+
751
+ if final_doc_raw:
752
+ # Extract payload (where Mem0 stores the actual memory data)
753
+ payload = final_doc_raw.get("payload", {})
754
+
755
+ # Build normalized memory document (same format as get() method)
756
+ final_doc = {
757
+ "id": str(final_doc_raw["_id"]), # Convert _id to id for API consistency
758
+ "memory": payload.get("memory") or payload.get("text"),
759
+ "text": payload.get("text") or payload.get("memory"),
760
+ "metadata": payload.get("metadata", {}),
761
+ "user_id": payload.get("user_id") or payload.get("metadata", {}).get("user_id"),
762
+ "created_at": payload.get("created_at"),
763
+ "updated_at": payload.get("updated_at"),
764
+ }
578
765
 
579
- if result is None:
580
- logger.warning(
581
- f"Mem0 update returned None for memory {memory_id}",
582
- extra={"memory_id": memory_id, "user_id": user_id},
583
- )
584
- return None
766
+ # Add any other payload fields
767
+ for key, value in payload.items():
768
+ if key not in [
769
+ "memory",
770
+ "text",
771
+ "metadata",
772
+ "user_id",
773
+ "created_at",
774
+ "updated_at",
775
+ ]:
776
+ final_doc[key] = value
777
+
778
+ # Ensure date objects are serialized if your downstream expects strings
779
+ if isinstance(final_doc.get("created_at"), datetime):
780
+ final_doc["created_at"] = final_doc["created_at"].isoformat()
781
+ if isinstance(final_doc.get("updated_at"), datetime):
782
+ final_doc["updated_at"] = final_doc["updated_at"].isoformat()
783
+ else:
784
+ final_doc = None
585
785
 
586
786
  logger.info(
587
- f"Successfully updated memory {memory_id} using Mem0 update method",
787
+ f"Successfully updated memory {memory_id}",
588
788
  extra={
589
789
  "memory_id": memory_id,
590
790
  "content_updated": bool(normalized_memory),
591
791
  "metadata_updated": bool(normalized_metadata),
592
792
  },
593
793
  )
594
- return result
794
+ return final_doc
595
795
 
596
796
  except ValueError:
597
797
  # Re-raise validation errors as-is
598
798
  raise
599
- except (AttributeError, TypeError, ValueError, KeyError) as e:
600
- logger.exception(
601
- f"Error updating memory {memory_id}",
602
- extra={"memory_id": memory_id, "user_id": user_id},
603
- )
799
+ except BaseException as e:
800
+ # Catch any unexpected errors during update. We catch BaseException
801
+ # (not Exception) to ensure we always raise Mem0MemoryServiceError for
802
+ # consistent error handling, but we re-raise KeyboardInterrupt and SystemExit
803
+ # to allow proper shutdown.
804
+ if isinstance(e, KeyboardInterrupt | SystemExit):
805
+ raise
806
+ logger.exception(f"Critical error during memory update for {memory_id}")
604
807
  raise Mem0MemoryServiceError(f"Update failed: {e}") from e
605
808
 
606
809
  def _normalize_content_input(
@@ -609,122 +812,45 @@ class Mem0MemoryService:
609
812
  data: str | dict[str, Any] | None,
610
813
  messages: str | list[dict[str, str]] | None,
611
814
  ) -> str | None:
612
- """
613
- Normalize content input from various parameter formats.
614
-
615
- Priority: memory > data > messages
616
- """
617
- # Already have memory content
618
- if memory:
815
+ """Normalize content input from various parameter formats."""
816
+ if memory is not None:
619
817
  if not isinstance(memory, str):
620
- raise TypeError("memory parameter must be a string")
818
+ raise TypeError(f"memory parameter must be a string, got {type(memory).__name__}")
621
819
  return memory.strip() if memory.strip() else None
622
-
623
- # Check data parameter
624
- if data:
820
+ if data is not None:
625
821
  if isinstance(data, str):
626
822
  return data.strip() if data.strip() else None
627
- elif isinstance(data, dict):
628
- content = data.get("memory") or data.get("text") or data.get("content")
629
- if content and isinstance(content, str):
630
- return content.strip() if content.strip() else None
631
-
632
- # Check messages parameter
633
- if messages:
823
+ if isinstance(data, dict):
824
+ return data.get("memory") or data.get("text") or data.get("content")
825
+ raise TypeError(f"data parameter must be a string or dict, got {type(data).__name__}")
826
+ if messages is not None:
634
827
  if isinstance(messages, str):
635
828
  return messages.strip() if messages.strip() else None
636
- elif isinstance(messages, list):
637
- content_parts = []
638
- for msg in messages:
639
- if isinstance(msg, dict) and "content" in msg:
640
- content = msg["content"]
641
- if isinstance(content, str) and content.strip():
642
- content_parts.append(content.strip())
643
- if content_parts:
644
- return " ".join(content_parts)
645
-
829
+ if isinstance(messages, list):
830
+ return " ".join([m.get("content", "") for m in messages if isinstance(m, dict)])
831
+ raise TypeError(
832
+ f"messages parameter must be a string or list, got {type(messages).__name__}"
833
+ )
646
834
  return None
647
835
 
648
836
  def _normalize_metadata_input(
649
837
  self, metadata: dict[str, Any] | None, data: dict[str, Any] | None
650
838
  ) -> dict[str, Any] | None:
651
839
  """Normalize metadata input, extracting from data dict if needed."""
652
- if metadata is not None and not isinstance(metadata, dict):
653
- raise TypeError("metadata must be a dict or None")
654
-
655
- # If metadata provided directly, use it
656
840
  if metadata is not None:
841
+ if not isinstance(metadata, dict):
842
+ raise TypeError(f"metadata parameter must be a dict, got {type(metadata).__name__}")
657
843
  return metadata
658
-
659
- # Check if metadata is in data dict
660
- if isinstance(data, dict) and "metadata" in data:
661
- data_metadata = data.get("metadata")
662
- if isinstance(data_metadata, dict):
663
- return data_metadata
664
-
844
+ if data is not None and isinstance(data, dict):
845
+ metadata_from_data = data.get("metadata")
846
+ if metadata_from_data is not None and not isinstance(metadata_from_data, dict):
847
+ raise TypeError(
848
+ f"metadata in data parameter must be a dict, "
849
+ f"got {type(metadata_from_data).__name__}"
850
+ )
851
+ return metadata_from_data
665
852
  return None
666
853
 
667
- def _update_via_mem0(
668
- self,
669
- memory_id: str,
670
- user_id: str | None,
671
- memory: str | None,
672
- metadata: dict[str, Any] | None,
673
- **kwargs,
674
- ) -> dict[str, Any] | None:
675
- """
676
- Update memory using Mem0's built-in update method.
677
-
678
- Note: Mem0's update() only supports content updates (text parameter).
679
- Metadata updates are not supported by the Mem0 API.
680
-
681
- Args:
682
- memory_id: Memory ID to update
683
- user_id: User ID for scoping (not used in update call)
684
- memory: New memory content (normalized)
685
- metadata: Metadata to merge (ignored - not supported by Mem0 update API)
686
- **kwargs: Additional arguments passed to Mem0
687
-
688
- Returns:
689
- Updated memory dict or None if not found
690
- """
691
- # Filter out user_id from kwargs to prevent passing it as a direct parameter
692
- filtered_kwargs = {k: v for k, v in kwargs.items() if k != "user_id"}
693
-
694
- logger.debug(
695
- f"Calling mem0.update() for memory_id={memory_id}",
696
- extra={
697
- "memory_id": memory_id,
698
- "has_content": bool(memory),
699
- "has_metadata": bool(metadata),
700
- "user_id": user_id,
701
- },
702
- )
703
-
704
- if metadata:
705
- logger.warning(
706
- f"Metadata update requested for memory {memory_id} but Mem0 update() "
707
- f"does not support metadata parameter. Metadata will not be updated."
708
- )
709
-
710
- update_kwargs = {"memory_id": memory_id}
711
- if memory:
712
- update_kwargs["data"] = memory
713
- update_kwargs.update(filtered_kwargs)
714
-
715
- result = self.memory.update(**update_kwargs)
716
-
717
- # Note: Mem0's update() doesn't support metadata parameter
718
- # Metadata updates would require direct MongoDB access, which we avoid
719
-
720
- # Normalize result to dict
721
- if isinstance(result, dict):
722
- return result
723
- elif isinstance(result, list) and len(result) > 0:
724
- return result[0] if isinstance(result[0], dict) else None
725
- else:
726
- return self.get(memory_id=memory_id)
727
-
728
854
  def _normalize_result(self, result: Any) -> list[dict[str, Any]]:
729
855
  """Normalize Mem0's return type (dict vs list)."""
730
856
  if result is None:
@@ -740,5 +866,35 @@ class Mem0MemoryService:
740
866
  return []
741
867
 
742
868
 
743
- def get_memory_service(mongo_uri, db_name, app_slug, config=None):
744
- return Mem0MemoryService(mongo_uri, db_name, app_slug, config)
869
+ def get_memory_service(
870
+ mongo_uri: str,
871
+ db_name: str,
872
+ app_slug: str,
873
+ config: dict[str, Any] | None = None,
874
+ provider: str = "mem0",
875
+ ) -> BaseMemoryService:
876
+ """
877
+ Factory function to create a memory service instance.
878
+
879
+ Args:
880
+ mongo_uri: MongoDB connection URI
881
+ db_name: Database name
882
+ app_slug: Application slug for scoping
883
+ config: Memory service configuration dictionary
884
+ provider: Memory provider to use (default: "mem0")
885
+
886
+ Returns:
887
+ BaseMemoryService instance (concrete implementation based on provider)
888
+
889
+ Raises:
890
+ ValueError: If provider is not supported
891
+ Mem0MemoryServiceError: If Mem0 provider fails to initialize
892
+ """
893
+ if provider == "mem0":
894
+ return Mem0MemoryService(mongo_uri, db_name, app_slug, config)
895
+ else:
896
+ raise ValueError(
897
+ f"Unsupported memory provider: {provider}. "
898
+ f"Supported providers: mem0. "
899
+ f"Future providers can be added by implementing BaseMemoryService."
900
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mdb-engine
3
- Version: 0.7.3
3
+ Version: 0.7.4
4
4
  Summary: MongoDB Engine
5
5
  Home-page: https://github.com/ranfysvalle02/mdb-engine
6
6
  Author: Fabian Valle
@@ -1,8 +1,8 @@
1
1
  mdb_engine/README.md,sha256=T3EFGcPopY9LslYW3lxgG3hohWkAOmBNbYG0FDMUJiY,3502
2
- mdb_engine/__init__.py,sha256=AwWt-MyzGNDl_2YEkTO0-x15qnUw3tF-oiOJJAYGkIs,3097
2
+ mdb_engine/__init__.py,sha256=OkNHx17I4f8jI5IAJGK0HbSTrZrUVnl1QlvtPZLri-I,3097
3
3
  mdb_engine/config.py,sha256=DTAyxfKB8ogyI0v5QR9Y-SJOgXQr_eDBCKxNBSqEyLc,7269
4
4
  mdb_engine/constants.py,sha256=eaotvW57TVOg7rRbLziGrVNoP7adgw_G9iVByHezc_A,7837
5
- mdb_engine/dependencies.py,sha256=MJuYQhZ9ZGzXlip1ha5zba9Rvn04HDPWahJFJH81Q2s,14107
5
+ mdb_engine/dependencies.py,sha256=F2_hbtK-lyeqB_iPeJVCh8jFbHi7sx0fduCUCNCKoUc,14128
6
6
  mdb_engine/exceptions.py,sha256=NkBSdTBNP9bVTtI6w6mAoEfeVZDq-Bg7vCF2L26ZDZo,8442
7
7
  mdb_engine/auth/ARCHITECTURE.md,sha256=JXZsjEIpNz4Szk18KaOiEabhEuz1pmYWXQRN-EJEHDM,4076
8
8
  mdb_engine/auth/README.md,sha256=IVlUOat96V3yM6wk4T-u4GxJ4-WF05eBSJBlvIexyBo,37285
@@ -35,7 +35,7 @@ mdb_engine/auth/utils.py,sha256=YkexCo0xV37mpOJUI32cntRHVOUUS7r19TIMPWHcgpA,2734
35
35
  mdb_engine/auth/websocket_sessions.py,sha256=7eFNagY2K3Rp1x7d_cO5JcpT-DrYkc__cmVhl6pAC2M,15081
36
36
  mdb_engine/auth/websocket_tickets.py,sha256=VoIArcnQBtYqXRMs-5m7NSvCJB1dEeHrLl7j7yG-H-A,9887
37
37
  mdb_engine/cli/__init__.py,sha256=PANRi4THmL34d1mawlqxIrnuItXMdqoMTq5Z1zHd7rM,301
38
- mdb_engine/cli/main.py,sha256=qDVVUJOXajdZmbId9AIGBS380U7OlhA8ILrAIfURbSE,811
38
+ mdb_engine/cli/main.py,sha256=4etDTeg4DIPQQ5HdWIwcpg9FWLx21NXF6f2fj4xG8gQ,811
39
39
  mdb_engine/cli/utils.py,sha256=bNRGJgdzxUjXAOVe1aoxWJ5M_IqtAE-eW4pfAkwiDDM,2760
40
40
  mdb_engine/cli/commands/__init__.py,sha256=ZSzMhKdV9ILD5EbOSxDV9nURHo1e4bQ0c8AWpqsTEqM,115
41
41
  mdb_engine/cli/commands/generate.py,sha256=VEcn7qNQkIPQlLEK3oXUBgYMwD-G0FyomXQcWTtKsbs,17304
@@ -48,13 +48,13 @@ mdb_engine/core/app_registration.py,sha256=w7wjrlNQsEekuDQkG6HM4Z5t00E65baEzncnh
48
48
  mdb_engine/core/app_secrets.py,sha256=bo-syg9UUATibNyXEZs-0TTYWG-JaY-2S0yNSGA12n0,10524
49
49
  mdb_engine/core/connection.py,sha256=XnwuPG34pJ7kJGJ84T0mhj1UZ6_CLz_9qZf6NRYGIS8,8346
50
50
  mdb_engine/core/encryption.py,sha256=RZ5LPF5g28E3ZBn6v1IMw_oas7u9YGFtBcEj8lTi9LM,7515
51
- mdb_engine/core/engine.py,sha256=wSJ99FP3NRUejGCXliwi9fW_pVZPpLjdUMG8FsU5Kms,185252
51
+ mdb_engine/core/engine.py,sha256=tzR5Gh5wTv9ACPYF7Z7aaAUInbF0l0Dtj2HrMB4VFes,185326
52
52
  mdb_engine/core/index_management.py,sha256=9-r7MIy3JnjQ35sGqsbj8K_I07vAUWtAVgSWC99lJcE,5555
53
53
  mdb_engine/core/manifest.py,sha256=D3OGRjm1It8dmS3IMoxHckHUerhs5PtcQgPz8o5ZL9w,140785
54
54
  mdb_engine/core/ray_integration.py,sha256=csAgICl2g7Plqy4N49MJU1Ca-lr8KZznAouXMMRwhG8,13706
55
55
  mdb_engine/core/seeding.py,sha256=c5IhdwlqUf_4Q5FFTAhPLaHPaUr_Txo3z_DUwZmWsFs,6421
56
- mdb_engine/core/service_initialization.py,sha256=N4varRswExBYahAgPfcPtNhqszOjm0JQ0u7r7mfFyqM,15018
57
- mdb_engine/core/types.py,sha256=u0LDGE4A3txGDlc9PNoQX8jwVYFjJSw4K5LDnpatsNc,11254
56
+ mdb_engine/core/service_initialization.py,sha256=LVCbc911YA07cTdy4UbPC7eho3L_Ub_8aX-HIp4MHq4,15145
57
+ mdb_engine/core/types.py,sha256=MAFAqIR-Oj8CF24jlVnOQevrlXOcME3npIgRAzfZXcA,11254
58
58
  mdb_engine/database/README.md,sha256=-31mVxBeVQaYsF3AD1-gQbD2NCYVcPjdFoA6sZ6b02Y,19354
59
59
  mdb_engine/database/__init__.py,sha256=rrc3eZFli3K2zrvVdDbMBi8YkmoHYzP6JNT0AUBE5VU,981
60
60
  mdb_engine/database/abstraction.py,sha256=H6f2WYY80r3onqN6s139uDSyG9W_QpadaoQ84hJuG1E,23438
@@ -74,9 +74,10 @@ mdb_engine/indexes/README.md,sha256=r7duq-1vtqHhBk1cwoBMYYh_dfTzxiaQaPE3mLB_3JQ,
74
74
  mdb_engine/indexes/__init__.py,sha256=9QFJ6qo_yD26dZcKyKjj-hhesFpaomBt-mWTtYTQvqc,613
75
75
  mdb_engine/indexes/helpers.py,sha256=tJHqDm18jKLrW-P2ofmnUnaf4_-V5xDLjqP_WU8W9MM,4190
76
76
  mdb_engine/indexes/manager.py,sha256=ekrYBfKD-GOBpZMjXIZWSQ7UPLgrD5BINYzVteWkSI8,32508
77
- mdb_engine/memory/README.md,sha256=Xa7pYut4zgZyRcb2TALcucMVAHCAu9uNGBKSwE7stY8,14764
78
- mdb_engine/memory/__init__.py,sha256=e4kAYgxd_-WAH8GovTwjEBO9hvASu_kXEupMgksAL-U,1008
79
- mdb_engine/memory/service.py,sha256=huWIjHAsV-g_Cs9djRkd8v5ZgnqiXqtNEbNjAgMfzs4,28846
77
+ mdb_engine/memory/README.md,sha256=3NqI-Sm8Cy-4lsU1wAWxSg92XpRFK19OfDU_dLo3go0,20581
78
+ mdb_engine/memory/__init__.py,sha256=VEB8PuNBB6q1mSlIYXv9xWt14YmxdiSV5D9hrgmMW94,1328
79
+ mdb_engine/memory/base.py,sha256=bI1Rl14mEAsRSMKe8_3jxzrs-N8LGdYWPwXXRziRDLs,5759
80
+ mdb_engine/memory/service.py,sha256=9uReFG5FaTIs7Fl6Kz7sXES4Uj_4ao7bgcfs32Nvd9U,37164
80
81
  mdb_engine/observability/README.md,sha256=CMgQaC1H8ESmCitfbhJifz6-XoXH_FPNE4MvuZ-oFas,13085
81
82
  mdb_engine/observability/__init__.py,sha256=jjLsrW6Gy2ayrbfLrgHsDB61NxWWkYLHwv0q-N3fxjA,1213
82
83
  mdb_engine/observability/health.py,sha256=ORjxF_rHYA9vCoxUqel5p0n9g3PLmHsHQn68Q402b6g,9212
@@ -91,9 +92,9 @@ mdb_engine/routing/__init__.py,sha256=reupjHi_RTc2ZBA4AH5XzobAmqy4EQIsfSUcTkFknU
91
92
  mdb_engine/routing/websockets.py,sha256=w0m1XMpXKPpPrEBcZNIj8B8PwWeTNrseUZE1P8cipdM,53441
92
93
  mdb_engine/utils/__init__.py,sha256=lDxQSGqkV4fVw5TWIk6FA6_eey_ZnEtMY0fir3cpAe8,236
93
94
  mdb_engine/utils/mongo.py,sha256=Oqtv4tQdpiiZzrilGLEYQPo8Vmh8WsTQypxQs8Of53s,3369
94
- mdb_engine-0.7.3.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
95
- mdb_engine-0.7.3.dist-info/METADATA,sha256=3XBbRELougI8lJsaJ1A02SuhIpsv2ojQspD2XidEXbo,18843
96
- mdb_engine-0.7.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
97
- mdb_engine-0.7.3.dist-info/entry_points.txt,sha256=INCbYdFbBzJalwPwxliEzLmPfR57IvQ7RAXG_pn8cL8,48
98
- mdb_engine-0.7.3.dist-info/top_level.txt,sha256=PH0UEBwTtgkm2vWvC9He_EOMn7hVn_Wg_Jyc0SmeO8k,11
99
- mdb_engine-0.7.3.dist-info/RECORD,,
95
+ mdb_engine-0.7.4.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
96
+ mdb_engine-0.7.4.dist-info/METADATA,sha256=T67AUeMaWC1ybxcQRAbPlYgKv9S5yWOJpWsIGCJVyrI,18843
97
+ mdb_engine-0.7.4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
98
+ mdb_engine-0.7.4.dist-info/entry_points.txt,sha256=INCbYdFbBzJalwPwxliEzLmPfR57IvQ7RAXG_pn8cL8,48
99
+ mdb_engine-0.7.4.dist-info/top_level.txt,sha256=PH0UEBwTtgkm2vWvC9He_EOMn7hVn_Wg_Jyc0SmeO8k,11
100
+ mdb_engine-0.7.4.dist-info/RECORD,,