mdb-engine 0.7.1__tar.gz → 0.7.2__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.
- {mdb_engine-0.7.1/mdb_engine.egg-info → mdb_engine-0.7.2}/PKG-INFO +1 -1
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/__init__.py +7 -7
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/cli/main.py +1 -1
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/memory/README.md +34 -3
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/memory/service.py +284 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2/mdb_engine.egg-info}/PKG-INFO +1 -1
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/pyproject.toml +1 -1
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/setup.py +1 -1
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/LICENSE +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/MANIFEST.in +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/README.md +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/README.md +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/ARCHITECTURE.md +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/README.md +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/__init__.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/audit.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/base.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/casbin_factory.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/casbin_models.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/config_defaults.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/config_helpers.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/cookie_utils.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/csrf.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/decorators.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/dependencies.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/helpers.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/integration.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/jwt.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/middleware.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/oso_factory.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/provider.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/rate_limiter.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/restrictions.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/session_manager.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/shared_middleware.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/shared_users.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/token_lifecycle.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/token_store.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/users.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/utils.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/websocket_sessions.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/auth/websocket_tickets.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/cli/__init__.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/cli/commands/__init__.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/cli/commands/generate.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/cli/commands/migrate.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/cli/commands/show.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/cli/commands/validate.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/cli/utils.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/config.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/constants.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/core/README.md +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/core/__init__.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/core/app_registration.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/core/app_secrets.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/core/connection.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/core/encryption.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/core/engine.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/core/index_management.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/core/manifest.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/core/ray_integration.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/core/seeding.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/core/service_initialization.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/core/types.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/database/README.md +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/database/__init__.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/database/abstraction.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/database/connection.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/database/query_validator.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/database/resource_limiter.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/database/scoped_wrapper.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/dependencies.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/di/__init__.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/di/container.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/di/providers.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/di/scopes.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/embeddings/README.md +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/embeddings/__init__.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/embeddings/dependencies.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/embeddings/service.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/exceptions.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/indexes/README.md +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/indexes/__init__.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/indexes/helpers.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/indexes/manager.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/memory/__init__.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/observability/README.md +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/observability/__init__.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/observability/health.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/observability/logging.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/observability/metrics.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/repositories/__init__.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/repositories/base.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/repositories/mongo.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/repositories/unit_of_work.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/routing/README.md +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/routing/__init__.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/routing/websockets.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/utils/__init__.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine/utils/mongo.py +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine.egg-info/SOURCES.txt +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine.egg-info/dependency_links.txt +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine.egg-info/entry_points.txt +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine.egg-info/requires.txt +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/mdb_engine.egg-info/top_level.txt +0 -0
- {mdb_engine-0.7.1 → mdb_engine-0.7.2}/setup.cfg +0 -0
|
@@ -82,13 +82,13 @@ from .repositories import Entity, MongoRepository, Repository, UnitOfWork
|
|
|
82
82
|
from .utils import clean_mongo_doc, clean_mongo_docs
|
|
83
83
|
|
|
84
84
|
__version__ = (
|
|
85
|
-
"0.7.
|
|
86
|
-
# -
|
|
87
|
-
# -
|
|
88
|
-
# -
|
|
89
|
-
# -
|
|
90
|
-
# -
|
|
91
|
-
# -
|
|
85
|
+
"0.7.2" # Memory service update functionality
|
|
86
|
+
# - ADDED: Memory service update() method for in-place memory updates
|
|
87
|
+
# - ADDED: Support for updating memory content and metadata while preserving IDs
|
|
88
|
+
# - ADDED: Automatic embedding recomputation via Mem0's update method
|
|
89
|
+
# - ADDED: Comprehensive unit tests for memory update functionality (17 tests)
|
|
90
|
+
# - ENHANCED: Memory service now uses Mem0's native update method exclusively
|
|
91
|
+
# - REMOVED: Direct MongoDB update fallback (simplified implementation)
|
|
92
92
|
)
|
|
93
93
|
|
|
94
94
|
__all__ = [
|
|
@@ -181,18 +181,49 @@ memories = await memory_service.get_all(
|
|
|
181
181
|
|
|
182
182
|
### Update Memory
|
|
183
183
|
|
|
184
|
-
Update existing memories:
|
|
184
|
+
Update existing memories in-place while preserving the original memory ID and creation timestamp:
|
|
185
185
|
|
|
186
186
|
```python
|
|
187
|
-
# Update memory
|
|
188
|
-
updated =
|
|
187
|
+
# Update memory content and metadata
|
|
188
|
+
updated = memory_service.update(
|
|
189
|
+
memory_id="memory_123",
|
|
190
|
+
user_id="user123",
|
|
191
|
+
memory="Updated memory content",
|
|
192
|
+
metadata={"updated": True, "category": "technical"}
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Update using messages format
|
|
196
|
+
updated = memory_service.update(
|
|
189
197
|
memory_id="memory_123",
|
|
190
198
|
user_id="user123",
|
|
191
199
|
messages=[{"role": "user", "content": "Updated content"}],
|
|
192
200
|
metadata={"updated": True}
|
|
193
201
|
)
|
|
202
|
+
|
|
203
|
+
# Update only metadata (content unchanged)
|
|
204
|
+
updated = memory_service.update(
|
|
205
|
+
memory_id="memory_123",
|
|
206
|
+
user_id="user123",
|
|
207
|
+
metadata={"category": "updated"}
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Backward compatibility: using 'data' parameter
|
|
211
|
+
updated = memory_service.update(
|
|
212
|
+
memory_id="memory_123",
|
|
213
|
+
user_id="user123",
|
|
214
|
+
data="Updated content",
|
|
215
|
+
metadata={"updated": True}
|
|
216
|
+
)
|
|
194
217
|
```
|
|
195
218
|
|
|
219
|
+
**Key Features:**
|
|
220
|
+
- **Preserves Memory ID**: The original memory ID is maintained
|
|
221
|
+
- **Preserves Creation Timestamp**: `created_at` is not modified
|
|
222
|
+
- **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)
|
|
225
|
+
- **Partial Updates**: Can update content only, metadata only, or both
|
|
226
|
+
|
|
196
227
|
### Delete Memory
|
|
197
228
|
|
|
198
229
|
Remove memories:
|
|
@@ -44,6 +44,20 @@ class Mem0MemoryServiceError(Exception):
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
class Mem0MemoryService:
|
|
47
|
+
"""
|
|
48
|
+
Production-ready Mem0 Memory Service with MongoDB integration.
|
|
49
|
+
|
|
50
|
+
Features:
|
|
51
|
+
- In-place memory updates preserving IDs and timestamps
|
|
52
|
+
- Automatic embedding recomputation on content changes
|
|
53
|
+
- Knowledge graph support (if enabled in Mem0 config)
|
|
54
|
+
- Comprehensive error handling and logging
|
|
55
|
+
- Backward compatibility with existing code
|
|
56
|
+
|
|
57
|
+
All operations go through Mem0's API to ensure proper state management,
|
|
58
|
+
graph updates, and relationship handling.
|
|
59
|
+
"""
|
|
60
|
+
|
|
47
61
|
def __init__(
|
|
48
62
|
self,
|
|
49
63
|
mongo_uri: str,
|
|
@@ -54,6 +68,9 @@ class Mem0MemoryService:
|
|
|
54
68
|
if not _check_mem0_available():
|
|
55
69
|
raise Mem0MemoryServiceError("Mem0 not installed. pip install mem0ai")
|
|
56
70
|
|
|
71
|
+
if not mongo_uri or not db_name or not app_slug:
|
|
72
|
+
raise Mem0MemoryServiceError("mongo_uri, db_name, and app_slug are required parameters")
|
|
73
|
+
|
|
57
74
|
self.mongo_uri = mongo_uri
|
|
58
75
|
self.db_name = db_name
|
|
59
76
|
self.app_slug = app_slug
|
|
@@ -461,6 +478,273 @@ class Mem0MemoryService:
|
|
|
461
478
|
):
|
|
462
479
|
return False
|
|
463
480
|
|
|
481
|
+
def update(
|
|
482
|
+
self,
|
|
483
|
+
memory_id: str,
|
|
484
|
+
user_id: str | None = None,
|
|
485
|
+
memory: str | None = None,
|
|
486
|
+
data: str | dict[str, Any] | None = None,
|
|
487
|
+
messages: str | list[dict[str, str]] | None = None,
|
|
488
|
+
metadata: dict[str, Any] | None = None,
|
|
489
|
+
**kwargs,
|
|
490
|
+
) -> dict[str, Any] | None:
|
|
491
|
+
"""
|
|
492
|
+
Update an existing memory in-place with production-grade error handling.
|
|
493
|
+
|
|
494
|
+
Updates the memory content and/or metadata while preserving:
|
|
495
|
+
- Original memory ID (never changes)
|
|
496
|
+
- Creation timestamp (created_at) - preserved
|
|
497
|
+
- Other existing fields - preserved unless explicitly updated
|
|
498
|
+
|
|
499
|
+
If content is updated, the embedding vector is automatically recomputed.
|
|
500
|
+
|
|
501
|
+
Args:
|
|
502
|
+
memory_id: The ID of the memory to update (required)
|
|
503
|
+
user_id: The user ID who owns the memory (for scoping and security)
|
|
504
|
+
memory: New memory content as a string (optional)
|
|
505
|
+
data: Alternative parameter name for memory content (backward compatibility).
|
|
506
|
+
Can be a string or dict with 'memory'/'text'/'content' key.
|
|
507
|
+
messages: Alternative way to provide content as messages (optional).
|
|
508
|
+
Can be a string or list of dicts with 'content' key.
|
|
509
|
+
metadata: Metadata updates to merge with existing metadata (optional).
|
|
510
|
+
Merged, not replaced - existing keys are preserved unless overridden.
|
|
511
|
+
**kwargs: Additional arguments passed to Mem0 operations
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
Updated memory object with same ID, or None if memory not found
|
|
515
|
+
|
|
516
|
+
Raises:
|
|
517
|
+
Mem0MemoryServiceError: If update operation fails
|
|
518
|
+
ValueError: If memory_id is invalid or empty
|
|
519
|
+
|
|
520
|
+
Example:
|
|
521
|
+
```python
|
|
522
|
+
# Update content and metadata
|
|
523
|
+
updated = memory_service.update(
|
|
524
|
+
memory_id="04f78986-dfad-46fe-8381-034bbee9a2fc",
|
|
525
|
+
user_id="user123",
|
|
526
|
+
memory="I love Python programming",
|
|
527
|
+
metadata={"category": "technical", "updated": True}
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
# Update only metadata (content unchanged)
|
|
531
|
+
updated = memory_service.update(
|
|
532
|
+
memory_id="04f78986-dfad-46fe-8381-034bbee9a2fc",
|
|
533
|
+
user_id="user123",
|
|
534
|
+
metadata={"category": "updated"}
|
|
535
|
+
)
|
|
536
|
+
```
|
|
537
|
+
"""
|
|
538
|
+
# Input validation
|
|
539
|
+
if not memory_id or not isinstance(memory_id, str) or not memory_id.strip():
|
|
540
|
+
raise ValueError("memory_id is required and must be a non-empty string")
|
|
541
|
+
|
|
542
|
+
try:
|
|
543
|
+
# Normalize data parameter (backward compatibility)
|
|
544
|
+
normalized_memory = self._normalize_content_input(memory, data, messages)
|
|
545
|
+
normalized_metadata = self._normalize_metadata_input(metadata, data)
|
|
546
|
+
|
|
547
|
+
# Verify memory exists before attempting update
|
|
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
|
+
)
|
|
554
|
+
return None
|
|
555
|
+
|
|
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
|
+
)
|
|
570
|
+
|
|
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
|
+
)
|
|
578
|
+
|
|
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
|
|
585
|
+
|
|
586
|
+
logger.info(
|
|
587
|
+
f"Successfully updated memory {memory_id} using Mem0 update method",
|
|
588
|
+
extra={
|
|
589
|
+
"memory_id": memory_id,
|
|
590
|
+
"content_updated": bool(normalized_memory),
|
|
591
|
+
"metadata_updated": bool(normalized_metadata),
|
|
592
|
+
},
|
|
593
|
+
)
|
|
594
|
+
return result
|
|
595
|
+
|
|
596
|
+
except ValueError:
|
|
597
|
+
# Re-raise validation errors as-is
|
|
598
|
+
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
|
+
)
|
|
604
|
+
raise Mem0MemoryServiceError(f"Update failed: {e}") from e
|
|
605
|
+
|
|
606
|
+
def _normalize_content_input(
|
|
607
|
+
self,
|
|
608
|
+
memory: str | None,
|
|
609
|
+
data: str | dict[str, Any] | None,
|
|
610
|
+
messages: str | list[dict[str, str]] | None,
|
|
611
|
+
) -> 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:
|
|
619
|
+
if not isinstance(memory, str):
|
|
620
|
+
raise TypeError("memory parameter must be a string")
|
|
621
|
+
return memory.strip() if memory.strip() else None
|
|
622
|
+
|
|
623
|
+
# Check data parameter
|
|
624
|
+
if data:
|
|
625
|
+
if isinstance(data, str):
|
|
626
|
+
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:
|
|
634
|
+
if isinstance(messages, str):
|
|
635
|
+
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
|
+
|
|
646
|
+
return None
|
|
647
|
+
|
|
648
|
+
def _normalize_metadata_input(
|
|
649
|
+
self, metadata: dict[str, Any] | None, data: dict[str, Any] | None
|
|
650
|
+
) -> dict[str, Any] | None:
|
|
651
|
+
"""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
|
+
if metadata is not None:
|
|
657
|
+
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
|
+
|
|
665
|
+
return None
|
|
666
|
+
|
|
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
|
+
This is the primary update path. Mem0's update method handles:
|
|
679
|
+
- In-place updates preserving memory ID and created_at timestamp
|
|
680
|
+
- Automatic embedding recomputation when content changes
|
|
681
|
+
- Metadata merging
|
|
682
|
+
- User scoping for security
|
|
683
|
+
|
|
684
|
+
Args:
|
|
685
|
+
memory_id: Memory ID to update
|
|
686
|
+
user_id: User ID for scoping
|
|
687
|
+
memory: New memory content (normalized)
|
|
688
|
+
metadata: Metadata to merge (normalized)
|
|
689
|
+
**kwargs: Additional arguments passed to Mem0
|
|
690
|
+
|
|
691
|
+
Returns:
|
|
692
|
+
Updated memory dict or None if not found
|
|
693
|
+
|
|
694
|
+
Raises:
|
|
695
|
+
Various exceptions from Mem0 if update fails
|
|
696
|
+
"""
|
|
697
|
+
# Build update parameters matching Mem0's API
|
|
698
|
+
# Mem0's update method signature:
|
|
699
|
+
# update(memory_id, text=None, metadata=None, user_id=None, **kwargs)
|
|
700
|
+
update_kwargs: dict[str, Any] = {"memory_id": memory_id}
|
|
701
|
+
|
|
702
|
+
# Add user_id for scoping (Mem0 supports this)
|
|
703
|
+
if user_id:
|
|
704
|
+
update_kwargs["user_id"] = str(user_id)
|
|
705
|
+
|
|
706
|
+
# Add text/content if provided
|
|
707
|
+
# Mem0 uses "text" parameter for content
|
|
708
|
+
if memory:
|
|
709
|
+
update_kwargs["text"] = memory
|
|
710
|
+
|
|
711
|
+
# Add metadata if provided
|
|
712
|
+
# Mem0 merges metadata automatically
|
|
713
|
+
if metadata is not None:
|
|
714
|
+
update_kwargs["metadata"] = metadata
|
|
715
|
+
|
|
716
|
+
# Pass through any additional kwargs
|
|
717
|
+
update_kwargs.update(kwargs)
|
|
718
|
+
|
|
719
|
+
logger.debug(
|
|
720
|
+
f"Calling mem0.update() for memory_id={memory_id}",
|
|
721
|
+
extra={
|
|
722
|
+
"memory_id": memory_id,
|
|
723
|
+
"has_content": bool(memory),
|
|
724
|
+
"has_metadata": bool(metadata),
|
|
725
|
+
"user_id": user_id,
|
|
726
|
+
},
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
# Call Mem0's update method directly
|
|
730
|
+
# This handles all the complexity: embedding recomputation, ID preservation, etc.
|
|
731
|
+
result = self.memory.update(**update_kwargs)
|
|
732
|
+
|
|
733
|
+
# Normalize result format
|
|
734
|
+
# Mem0 may return dict, list, or other formats
|
|
735
|
+
if isinstance(result, dict):
|
|
736
|
+
return result
|
|
737
|
+
elif isinstance(result, list) and len(result) > 0:
|
|
738
|
+
# If list, return first item
|
|
739
|
+
return result[0] if isinstance(result[0], dict) else None
|
|
740
|
+
else:
|
|
741
|
+
# If result is None or unexpected format, return None to trigger fallback
|
|
742
|
+
logger.debug(
|
|
743
|
+
f"Mem0 update returned unexpected format: {type(result)}",
|
|
744
|
+
extra={"memory_id": memory_id},
|
|
745
|
+
)
|
|
746
|
+
return None
|
|
747
|
+
|
|
464
748
|
def _normalize_result(self, result: Any) -> list[dict[str, Any]]:
|
|
465
749
|
"""Normalize Mem0's return type (dict vs list)."""
|
|
466
750
|
if result is None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|