memorygraphMCP 0.11.7__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.
- memorygraph/__init__.py +50 -0
- memorygraph/__main__.py +12 -0
- memorygraph/advanced_tools.py +509 -0
- memorygraph/analytics/__init__.py +46 -0
- memorygraph/analytics/advanced_queries.py +727 -0
- memorygraph/backends/__init__.py +21 -0
- memorygraph/backends/base.py +179 -0
- memorygraph/backends/cloud.py +75 -0
- memorygraph/backends/cloud_backend.py +858 -0
- memorygraph/backends/factory.py +577 -0
- memorygraph/backends/falkordb_backend.py +749 -0
- memorygraph/backends/falkordblite_backend.py +746 -0
- memorygraph/backends/ladybugdb_backend.py +242 -0
- memorygraph/backends/memgraph_backend.py +327 -0
- memorygraph/backends/neo4j_backend.py +298 -0
- memorygraph/backends/sqlite_fallback.py +463 -0
- memorygraph/backends/turso.py +448 -0
- memorygraph/cli.py +743 -0
- memorygraph/cloud_database.py +297 -0
- memorygraph/config.py +295 -0
- memorygraph/database.py +933 -0
- memorygraph/graph_analytics.py +631 -0
- memorygraph/integration/__init__.py +69 -0
- memorygraph/integration/context_capture.py +426 -0
- memorygraph/integration/project_analysis.py +583 -0
- memorygraph/integration/workflow_tracking.py +492 -0
- memorygraph/intelligence/__init__.py +59 -0
- memorygraph/intelligence/context_retrieval.py +447 -0
- memorygraph/intelligence/entity_extraction.py +386 -0
- memorygraph/intelligence/pattern_recognition.py +420 -0
- memorygraph/intelligence/temporal.py +374 -0
- memorygraph/migration/__init__.py +27 -0
- memorygraph/migration/manager.py +579 -0
- memorygraph/migration/models.py +142 -0
- memorygraph/migration/scripts/__init__.py +17 -0
- memorygraph/migration/scripts/bitemporal_migration.py +595 -0
- memorygraph/migration/scripts/multitenancy_migration.py +452 -0
- memorygraph/migration_tools_module.py +146 -0
- memorygraph/models.py +684 -0
- memorygraph/proactive/__init__.py +46 -0
- memorygraph/proactive/outcome_learning.py +444 -0
- memorygraph/proactive/predictive.py +410 -0
- memorygraph/proactive/session_briefing.py +399 -0
- memorygraph/relationships.py +668 -0
- memorygraph/server.py +883 -0
- memorygraph/sqlite_database.py +1876 -0
- memorygraph/tools/__init__.py +59 -0
- memorygraph/tools/activity_tools.py +262 -0
- memorygraph/tools/memory_tools.py +315 -0
- memorygraph/tools/migration_tools.py +181 -0
- memorygraph/tools/relationship_tools.py +147 -0
- memorygraph/tools/search_tools.py +406 -0
- memorygraph/tools/temporal_tools.py +339 -0
- memorygraph/utils/__init__.py +10 -0
- memorygraph/utils/context_extractor.py +429 -0
- memorygraph/utils/error_handling.py +151 -0
- memorygraph/utils/export_import.py +425 -0
- memorygraph/utils/graph_algorithms.py +200 -0
- memorygraph/utils/pagination.py +149 -0
- memorygraph/utils/project_detection.py +133 -0
- memorygraphmcp-0.11.7.dist-info/METADATA +970 -0
- memorygraphmcp-0.11.7.dist-info/RECORD +65 -0
- memorygraphmcp-0.11.7.dist-info/WHEEL +4 -0
- memorygraphmcp-0.11.7.dist-info/entry_points.txt +2 -0
- memorygraphmcp-0.11.7.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Temporal Memory - Track how information changes over time.
|
|
3
|
+
|
|
4
|
+
This module provides version tracking, memory history, and
|
|
5
|
+
temporal queries for understanding how knowledge evolves.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Optional
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TemporalMemory:
|
|
16
|
+
"""Handles temporal aspects of memories, including version tracking."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, backend):
|
|
19
|
+
"""
|
|
20
|
+
Initialize temporal memory with database backend.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
backend: Database backend instance
|
|
24
|
+
"""
|
|
25
|
+
self.backend = backend
|
|
26
|
+
|
|
27
|
+
async def get_memory_history(self, memory_id: str) -> list[dict]:
|
|
28
|
+
"""
|
|
29
|
+
Get complete version history for a memory by traversing PREVIOUS relationships.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
memory_id: ID of the memory to get history for
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
List of memory versions in chronological order (oldest to newest)
|
|
36
|
+
"""
|
|
37
|
+
query = """
|
|
38
|
+
MATCH path = (current:Memory {id: $memory_id})-[:PREVIOUS*0..]->(older:Memory)
|
|
39
|
+
WITH older, length(path) as depth
|
|
40
|
+
ORDER BY depth DESC
|
|
41
|
+
RETURN older.id as id,
|
|
42
|
+
older.title as title,
|
|
43
|
+
older.content as content,
|
|
44
|
+
older.type as type,
|
|
45
|
+
older.tags as tags,
|
|
46
|
+
older.created_at as created_at,
|
|
47
|
+
older.updated_at as updated_at,
|
|
48
|
+
older.is_current as is_current,
|
|
49
|
+
older.superseded_by as superseded_by,
|
|
50
|
+
depth
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
params = {"memory_id": memory_id}
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
results = await self.backend.execute_query(query, params)
|
|
57
|
+
|
|
58
|
+
history = []
|
|
59
|
+
for record in results:
|
|
60
|
+
version = {
|
|
61
|
+
"id": record["id"],
|
|
62
|
+
"title": record.get("title"),
|
|
63
|
+
"content": record.get("content"),
|
|
64
|
+
"type": record.get("type"),
|
|
65
|
+
"tags": record.get("tags", []),
|
|
66
|
+
"created_at": record.get("created_at"),
|
|
67
|
+
"updated_at": record.get("updated_at"),
|
|
68
|
+
"is_current": record.get("is_current", True),
|
|
69
|
+
"superseded_by": record.get("superseded_by"),
|
|
70
|
+
"version_depth": record["depth"],
|
|
71
|
+
}
|
|
72
|
+
history.append(version)
|
|
73
|
+
|
|
74
|
+
return history
|
|
75
|
+
|
|
76
|
+
except Exception as e:
|
|
77
|
+
logger.error(f"Error getting memory history for {memory_id}: {e}")
|
|
78
|
+
return []
|
|
79
|
+
|
|
80
|
+
async def get_state_at(
|
|
81
|
+
self, memory_id: str, timestamp: datetime
|
|
82
|
+
) -> Optional[dict]:
|
|
83
|
+
"""
|
|
84
|
+
Get the state of a memory at a specific point in time.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
memory_id: ID of the memory
|
|
88
|
+
timestamp: Target timestamp
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Memory state at that timestamp, or None if not found
|
|
92
|
+
"""
|
|
93
|
+
query = """
|
|
94
|
+
MATCH path = (current:Memory {id: $memory_id})-[:PREVIOUS*0..]->(older:Memory)
|
|
95
|
+
WHERE older.created_at <= $timestamp
|
|
96
|
+
WITH older, length(path) as depth
|
|
97
|
+
ORDER BY depth ASC, older.created_at DESC
|
|
98
|
+
LIMIT 1
|
|
99
|
+
RETURN older.id as id,
|
|
100
|
+
older.title as title,
|
|
101
|
+
older.content as content,
|
|
102
|
+
older.type as type,
|
|
103
|
+
older.tags as tags,
|
|
104
|
+
older.created_at as created_at,
|
|
105
|
+
older.updated_at as updated_at,
|
|
106
|
+
older.is_current as is_current
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
params = {"memory_id": memory_id, "timestamp": timestamp}
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
results = await self.backend.execute_query(query, params)
|
|
113
|
+
|
|
114
|
+
if not results:
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
record = results[0]
|
|
118
|
+
return {
|
|
119
|
+
"id": record["id"],
|
|
120
|
+
"title": record.get("title"),
|
|
121
|
+
"content": record.get("content"),
|
|
122
|
+
"type": record.get("type"),
|
|
123
|
+
"tags": record.get("tags", []),
|
|
124
|
+
"created_at": record.get("created_at"),
|
|
125
|
+
"updated_at": record.get("updated_at"),
|
|
126
|
+
"is_current": record.get("is_current", False),
|
|
127
|
+
"queried_at": timestamp,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
except Exception as e:
|
|
131
|
+
logger.error(f"Error getting state at {timestamp} for {memory_id}: {e}")
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
async def track_entity_changes(self, entity_id: str) -> list[dict]:
|
|
135
|
+
"""
|
|
136
|
+
Track how an entity has changed over time by finding all memories that mention it.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
entity_id: ID or text of the entity to track
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Timeline of changes to the entity
|
|
143
|
+
"""
|
|
144
|
+
query = """
|
|
145
|
+
MATCH (e:Entity)
|
|
146
|
+
WHERE e.id = $entity_id OR e.text = $entity_id
|
|
147
|
+
MATCH (m:Memory)-[r:MENTIONS]->(e)
|
|
148
|
+
OPTIONAL MATCH (m)-[:PREVIOUS]->(prev:Memory)
|
|
149
|
+
WITH m, e, prev,
|
|
150
|
+
CASE WHEN prev IS NOT NULL
|
|
151
|
+
THEN exists((prev)-[:MENTIONS]->(e))
|
|
152
|
+
ELSE false
|
|
153
|
+
END as was_mentioned_before
|
|
154
|
+
RETURN m.id as memory_id,
|
|
155
|
+
m.title as title,
|
|
156
|
+
m.content as content,
|
|
157
|
+
m.type as memory_type,
|
|
158
|
+
m.created_at as created_at,
|
|
159
|
+
m.updated_at as updated_at,
|
|
160
|
+
r.confidence as mention_confidence,
|
|
161
|
+
was_mentioned_before,
|
|
162
|
+
CASE WHEN m.is_current = true THEN 'current'
|
|
163
|
+
WHEN m.superseded_by IS NOT NULL THEN 'superseded'
|
|
164
|
+
ELSE 'active'
|
|
165
|
+
END as status
|
|
166
|
+
ORDER BY m.created_at ASC
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
params = {"entity_id": entity_id}
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
results = await self.backend.execute_query(query, params)
|
|
173
|
+
|
|
174
|
+
timeline = []
|
|
175
|
+
for record in results:
|
|
176
|
+
change = {
|
|
177
|
+
"memory_id": record["memory_id"],
|
|
178
|
+
"title": record.get("title"),
|
|
179
|
+
"content": record.get("content"),
|
|
180
|
+
"memory_type": record.get("memory_type"),
|
|
181
|
+
"created_at": record.get("created_at"),
|
|
182
|
+
"updated_at": record.get("updated_at"),
|
|
183
|
+
"mention_confidence": record.get("mention_confidence"),
|
|
184
|
+
"was_new_mention": not record.get("was_mentioned_before", False),
|
|
185
|
+
"status": record.get("status", "active"),
|
|
186
|
+
}
|
|
187
|
+
timeline.append(change)
|
|
188
|
+
|
|
189
|
+
return timeline
|
|
190
|
+
|
|
191
|
+
except Exception as e:
|
|
192
|
+
logger.error(f"Error tracking entity changes for {entity_id}: {e}")
|
|
193
|
+
return []
|
|
194
|
+
|
|
195
|
+
async def create_version(
|
|
196
|
+
self,
|
|
197
|
+
current_memory_id: str,
|
|
198
|
+
new_memory: dict,
|
|
199
|
+
) -> str:
|
|
200
|
+
"""
|
|
201
|
+
Create a new version of a memory, linking it to the previous version.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
current_memory_id: ID of the current memory to supersede
|
|
205
|
+
new_memory: New memory data
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
ID of the new memory version
|
|
209
|
+
"""
|
|
210
|
+
query = """
|
|
211
|
+
MATCH (current:Memory {id: $current_id})
|
|
212
|
+
CREATE (new:Memory)
|
|
213
|
+
SET new = $new_props,
|
|
214
|
+
new.id = randomUUID(),
|
|
215
|
+
new.created_at = datetime(),
|
|
216
|
+
new.updated_at = datetime(),
|
|
217
|
+
new.is_current = true,
|
|
218
|
+
current.is_current = false,
|
|
219
|
+
current.superseded_by = new.id
|
|
220
|
+
CREATE (new)-[:PREVIOUS {superseded_at: datetime()}]->(current)
|
|
221
|
+
RETURN new.id as new_id
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
params = {
|
|
225
|
+
"current_id": current_memory_id,
|
|
226
|
+
"new_props": new_memory,
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
try:
|
|
230
|
+
results = await self.backend.execute_query(query, params)
|
|
231
|
+
|
|
232
|
+
if results:
|
|
233
|
+
new_id = results[0]["new_id"]
|
|
234
|
+
logger.info(f"Created new version {new_id} for memory {current_memory_id}")
|
|
235
|
+
return new_id
|
|
236
|
+
else:
|
|
237
|
+
raise Exception("Failed to create new version")
|
|
238
|
+
|
|
239
|
+
except Exception as e:
|
|
240
|
+
logger.error(f"Error creating version for {current_memory_id}: {e}")
|
|
241
|
+
raise
|
|
242
|
+
|
|
243
|
+
async def get_version_diff(
|
|
244
|
+
self, version1_id: str, version2_id: str
|
|
245
|
+
) -> dict[str, dict]:
|
|
246
|
+
"""
|
|
247
|
+
Compare two versions of a memory and return differences.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
version1_id: ID of first version
|
|
251
|
+
version2_id: ID of second version
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
Dictionary of changes between versions
|
|
255
|
+
"""
|
|
256
|
+
query = """
|
|
257
|
+
MATCH (v1:Memory {id: $v1_id})
|
|
258
|
+
MATCH (v2:Memory {id: $v2_id})
|
|
259
|
+
RETURN v1.title as v1_title, v2.title as v2_title,
|
|
260
|
+
v1.content as v1_content, v2.content as v2_content,
|
|
261
|
+
v1.type as v1_type, v2.type as v2_type,
|
|
262
|
+
v1.tags as v1_tags, v2.tags as v2_tags,
|
|
263
|
+
v1.updated_at as v1_updated, v2.updated_at as v2_updated
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
params = {"v1_id": version1_id, "v2_id": version2_id}
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
results = await self.backend.execute_query(query, params)
|
|
270
|
+
|
|
271
|
+
if not results:
|
|
272
|
+
return {}
|
|
273
|
+
|
|
274
|
+
record = results[0]
|
|
275
|
+
diff = {}
|
|
276
|
+
|
|
277
|
+
# Compare fields
|
|
278
|
+
if record.get("v1_title") != record.get("v2_title"):
|
|
279
|
+
diff["title"] = {
|
|
280
|
+
"from": record.get("v1_title"),
|
|
281
|
+
"to": record.get("v2_title"),
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if record.get("v1_content") != record.get("v2_content"):
|
|
285
|
+
diff["content"] = {
|
|
286
|
+
"from": record.get("v1_content"),
|
|
287
|
+
"to": record.get("v2_content"),
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if record.get("v1_type") != record.get("v2_type"):
|
|
291
|
+
diff["type"] = {
|
|
292
|
+
"from": record.get("v1_type"),
|
|
293
|
+
"to": record.get("v2_type"),
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
v1_tags = set(record.get("v1_tags", []))
|
|
297
|
+
v2_tags = set(record.get("v2_tags", []))
|
|
298
|
+
if v1_tags != v2_tags:
|
|
299
|
+
diff["tags"] = {
|
|
300
|
+
"added": list(v2_tags - v1_tags),
|
|
301
|
+
"removed": list(v1_tags - v2_tags),
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return diff
|
|
305
|
+
|
|
306
|
+
except Exception as e:
|
|
307
|
+
logger.error(f"Error comparing versions {version1_id} and {version2_id}: {e}")
|
|
308
|
+
return {}
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
# Convenience functions
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
async def get_memory_history(backend, memory_id: str) -> list[dict]:
|
|
315
|
+
"""
|
|
316
|
+
Get version history for a memory.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
backend: Database backend instance
|
|
320
|
+
memory_id: ID of the memory
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
List of memory versions in chronological order
|
|
324
|
+
|
|
325
|
+
Example:
|
|
326
|
+
>>> history = await get_memory_history(backend, "memory-123")
|
|
327
|
+
>>> for version in history:
|
|
328
|
+
... print(f"Version created at {version['created_at']}")
|
|
329
|
+
"""
|
|
330
|
+
temporal = TemporalMemory(backend)
|
|
331
|
+
return await temporal.get_memory_history(memory_id)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
async def get_state_at(
|
|
335
|
+
backend, memory_id: str, timestamp: datetime
|
|
336
|
+
) -> Optional[dict]:
|
|
337
|
+
"""
|
|
338
|
+
Get memory state at a specific timestamp.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
backend: Database backend instance
|
|
342
|
+
memory_id: ID of the memory
|
|
343
|
+
timestamp: Target timestamp
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
Memory state at that time, or None
|
|
347
|
+
|
|
348
|
+
Example:
|
|
349
|
+
>>> from datetime import datetime, timedelta, timezone
|
|
350
|
+
>>> yesterday = datetime.now(timezone.utc) - timedelta(days=1)
|
|
351
|
+
>>> state = await get_state_at(backend, "memory-123", yesterday)
|
|
352
|
+
"""
|
|
353
|
+
temporal = TemporalMemory(backend)
|
|
354
|
+
return await temporal.get_state_at(memory_id, timestamp)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
async def track_entity_changes(backend, entity_id: str) -> list[dict]:
|
|
358
|
+
"""
|
|
359
|
+
Track entity changes over time.
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
backend: Database backend instance
|
|
363
|
+
entity_id: ID or text of the entity
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
Timeline of entity mentions and changes
|
|
367
|
+
|
|
368
|
+
Example:
|
|
369
|
+
>>> timeline = await track_entity_changes(backend, "React")
|
|
370
|
+
>>> for change in timeline:
|
|
371
|
+
... print(f"{change['created_at']}: {change['title']}")
|
|
372
|
+
"""
|
|
373
|
+
temporal = TemporalMemory(backend)
|
|
374
|
+
return await temporal.track_entity_changes(entity_id)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Backend migration module.
|
|
3
|
+
|
|
4
|
+
Provides utilities for migrating memories between different backend types
|
|
5
|
+
with validation, verification, and rollback capability.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .models import (
|
|
9
|
+
BackendType,
|
|
10
|
+
BackendConfig,
|
|
11
|
+
MigrationOptions,
|
|
12
|
+
ValidationResult,
|
|
13
|
+
VerificationResult,
|
|
14
|
+
MigrationResult
|
|
15
|
+
)
|
|
16
|
+
from .manager import MigrationManager, MigrationError
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"BackendType",
|
|
20
|
+
"BackendConfig",
|
|
21
|
+
"MigrationOptions",
|
|
22
|
+
"ValidationResult",
|
|
23
|
+
"VerificationResult",
|
|
24
|
+
"MigrationResult",
|
|
25
|
+
"MigrationManager",
|
|
26
|
+
"MigrationError",
|
|
27
|
+
]
|