AbstractMemory 0.0.1__py3-none-any.whl → 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- abstractmemory/__init__.py +744 -31
- abstractmemory/cognitive/__init__.py +1 -0
- abstractmemory/components/__init__.py +1 -0
- abstractmemory/components/core.py +112 -0
- abstractmemory/components/episodic.py +68 -0
- abstractmemory/components/semantic.py +102 -0
- abstractmemory/components/working.py +50 -0
- abstractmemory/core/__init__.py +1 -0
- abstractmemory/core/interfaces.py +95 -0
- abstractmemory/core/temporal.py +100 -0
- abstractmemory/embeddings/__init__.py +317 -0
- abstractmemory/graph/__init__.py +1 -0
- abstractmemory/graph/knowledge_graph.py +178 -0
- abstractmemory/simple.py +151 -0
- abstractmemory/storage/__init__.py +16 -0
- abstractmemory/storage/dual_manager.py +269 -0
- abstractmemory/storage/lancedb_storage.py +544 -0
- abstractmemory/storage/markdown_storage.py +447 -0
- abstractmemory-0.2.1.dist-info/METADATA +460 -0
- abstractmemory-0.2.1.dist-info/RECORD +23 -0
- {abstractmemory-0.0.1.dist-info → abstractmemory-0.2.1.dist-info}/licenses/LICENSE +4 -1
- abstractmemory-0.0.1.dist-info/METADATA +0 -94
- abstractmemory-0.0.1.dist-info/RECORD +0 -6
- {abstractmemory-0.0.1.dist-info → abstractmemory-0.2.1.dist-info}/WHEEL +0 -0
- {abstractmemory-0.0.1.dist-info → abstractmemory-0.2.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dual Storage Manager for managing multiple storage backends.
|
|
3
|
+
Supports markdown, lancedb, dual, or no storage modes.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Optional, Dict, List, Literal, Any
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
from ..core.interfaces import IStorage
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DualStorageManager:
|
|
16
|
+
"""
|
|
17
|
+
Manages multiple storage backends for verbatim interactions and experiential notes.
|
|
18
|
+
|
|
19
|
+
Modes:
|
|
20
|
+
- "markdown": Human-readable, observable, version-controllable
|
|
21
|
+
- "lancedb": SQL + vector search capabilities via AbstractCore embeddings
|
|
22
|
+
- "dual": Write to both, read from LanceDB for performance
|
|
23
|
+
- None: No persistence (default)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self,
|
|
27
|
+
mode: Optional[Literal["markdown", "lancedb", "dual"]] = None,
|
|
28
|
+
markdown_path: Optional[str] = None,
|
|
29
|
+
lancedb_uri: Optional[str] = None,
|
|
30
|
+
embedding_provider: Optional[Any] = None):
|
|
31
|
+
"""
|
|
32
|
+
Initialize storage manager.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
mode: Storage mode to use
|
|
36
|
+
markdown_path: Path for markdown storage
|
|
37
|
+
lancedb_uri: URI for LanceDB storage
|
|
38
|
+
embedding_provider: AbstractCore instance for embeddings
|
|
39
|
+
"""
|
|
40
|
+
self.mode = mode
|
|
41
|
+
self.embedding_provider = embedding_provider
|
|
42
|
+
|
|
43
|
+
# Initialize storage backends based on mode
|
|
44
|
+
self.markdown_storage = None
|
|
45
|
+
self.lancedb_storage = None
|
|
46
|
+
|
|
47
|
+
if mode in ["markdown", "dual"] and markdown_path:
|
|
48
|
+
try:
|
|
49
|
+
from .markdown_storage import MarkdownStorage
|
|
50
|
+
self.markdown_storage = MarkdownStorage(markdown_path)
|
|
51
|
+
logger.info(f"Initialized markdown storage at {markdown_path}")
|
|
52
|
+
except (ImportError, OSError, FileNotFoundError) as e:
|
|
53
|
+
logger.warning(f"Failed to initialize markdown storage: {e}")
|
|
54
|
+
self.markdown_storage = None
|
|
55
|
+
|
|
56
|
+
if mode in ["lancedb", "dual"] and lancedb_uri:
|
|
57
|
+
try:
|
|
58
|
+
from .lancedb_storage import LanceDBStorage
|
|
59
|
+
self.lancedb_storage = LanceDBStorage(lancedb_uri, embedding_provider)
|
|
60
|
+
logger.info(f"Initialized LanceDB storage at {lancedb_uri}")
|
|
61
|
+
except (ImportError, OSError, FileNotFoundError) as e:
|
|
62
|
+
logger.warning(f"Failed to initialize LanceDB storage: {e}")
|
|
63
|
+
self.lancedb_storage = None
|
|
64
|
+
|
|
65
|
+
def is_enabled(self) -> bool:
|
|
66
|
+
"""Check if any storage backend is enabled"""
|
|
67
|
+
return self.mode is not None and (self.markdown_storage is not None or self.lancedb_storage is not None)
|
|
68
|
+
|
|
69
|
+
def save_interaction(self, user_id: str, timestamp: datetime,
|
|
70
|
+
user_input: str, agent_response: str,
|
|
71
|
+
topic: str, metadata: Optional[Dict] = None) -> Optional[str]:
|
|
72
|
+
"""
|
|
73
|
+
Save verbatim interaction to enabled storage backends.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Interaction ID if successful, None if no storage enabled
|
|
77
|
+
"""
|
|
78
|
+
if not self.is_enabled():
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
interaction_id = None
|
|
82
|
+
|
|
83
|
+
# Save to markdown storage
|
|
84
|
+
if self.markdown_storage:
|
|
85
|
+
try:
|
|
86
|
+
interaction_id = self.markdown_storage.save_interaction(
|
|
87
|
+
user_id, timestamp, user_input, agent_response, topic, metadata
|
|
88
|
+
)
|
|
89
|
+
logger.debug(f"Saved interaction {interaction_id} to markdown storage")
|
|
90
|
+
except Exception as e:
|
|
91
|
+
logger.error(f"Failed to save interaction to markdown: {e}")
|
|
92
|
+
|
|
93
|
+
# Save to LanceDB storage
|
|
94
|
+
if self.lancedb_storage:
|
|
95
|
+
try:
|
|
96
|
+
# LanceDB storage handles embedding generation internally
|
|
97
|
+
ldb_id = self.lancedb_storage.save_interaction(
|
|
98
|
+
user_id, timestamp, user_input, agent_response, topic, metadata
|
|
99
|
+
)
|
|
100
|
+
if interaction_id is None: # Use LanceDB ID if markdown didn't provide one
|
|
101
|
+
interaction_id = ldb_id
|
|
102
|
+
|
|
103
|
+
logger.debug(f"Saved interaction {ldb_id} to LanceDB storage")
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.error(f"Failed to save interaction to LanceDB: {e}")
|
|
106
|
+
|
|
107
|
+
return interaction_id
|
|
108
|
+
|
|
109
|
+
def save_experiential_note(self, timestamp: datetime, reflection: str,
|
|
110
|
+
interaction_id: str, note_type: str = "reflection",
|
|
111
|
+
metadata: Optional[Dict] = None) -> Optional[str]:
|
|
112
|
+
"""
|
|
113
|
+
Save AI experiential note to enabled storage backends.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Note ID if successful, None if no storage enabled
|
|
117
|
+
"""
|
|
118
|
+
if not self.is_enabled():
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
note_id = None
|
|
122
|
+
|
|
123
|
+
# Save to markdown storage
|
|
124
|
+
if self.markdown_storage:
|
|
125
|
+
try:
|
|
126
|
+
note_id = self.markdown_storage.save_experiential_note(
|
|
127
|
+
timestamp, reflection, interaction_id, note_type, metadata
|
|
128
|
+
)
|
|
129
|
+
logger.debug(f"Saved experiential note {note_id} to markdown storage")
|
|
130
|
+
except Exception as e:
|
|
131
|
+
logger.error(f"Failed to save experiential note to markdown: {e}")
|
|
132
|
+
|
|
133
|
+
# Save to LanceDB storage
|
|
134
|
+
if self.lancedb_storage:
|
|
135
|
+
try:
|
|
136
|
+
# LanceDB storage handles embedding generation internally
|
|
137
|
+
ldb_note_id = self.lancedb_storage.save_experiential_note(
|
|
138
|
+
timestamp, reflection, interaction_id, note_type, metadata
|
|
139
|
+
)
|
|
140
|
+
if note_id is None: # Use LanceDB ID if markdown didn't provide one
|
|
141
|
+
note_id = ldb_note_id
|
|
142
|
+
|
|
143
|
+
logger.debug(f"Saved experiential note {ldb_note_id} to LanceDB storage")
|
|
144
|
+
except Exception as e:
|
|
145
|
+
logger.error(f"Failed to save experiential note to LanceDB: {e}")
|
|
146
|
+
|
|
147
|
+
return note_id
|
|
148
|
+
|
|
149
|
+
def link_interaction_to_note(self, interaction_id: str, note_id: str) -> None:
|
|
150
|
+
"""Create bidirectional link between interaction and experiential note"""
|
|
151
|
+
if not self.is_enabled():
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
# Link in markdown storage
|
|
155
|
+
if self.markdown_storage:
|
|
156
|
+
try:
|
|
157
|
+
self.markdown_storage.link_interaction_to_note(interaction_id, note_id)
|
|
158
|
+
logger.debug(f"Linked interaction {interaction_id} to note {note_id} in markdown")
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logger.error(f"Failed to create link in markdown: {e}")
|
|
161
|
+
|
|
162
|
+
# Link in LanceDB storage
|
|
163
|
+
if self.lancedb_storage:
|
|
164
|
+
try:
|
|
165
|
+
self.lancedb_storage.link_interaction_to_note(interaction_id, note_id)
|
|
166
|
+
logger.debug(f"Linked interaction {interaction_id} to note {note_id} in LanceDB")
|
|
167
|
+
except Exception as e:
|
|
168
|
+
logger.error(f"Failed to create link in LanceDB: {e}")
|
|
169
|
+
|
|
170
|
+
def search_interactions(self, query: str, user_id: Optional[str] = None,
|
|
171
|
+
start_date: Optional[datetime] = None,
|
|
172
|
+
end_date: Optional[datetime] = None) -> List[Dict]:
|
|
173
|
+
"""
|
|
174
|
+
Search interactions. Prefers LanceDB for performance, falls back to markdown.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
List of matching interactions
|
|
178
|
+
"""
|
|
179
|
+
if not self.is_enabled():
|
|
180
|
+
return []
|
|
181
|
+
|
|
182
|
+
# Prefer LanceDB for search if available (SQL + vector capabilities)
|
|
183
|
+
if self.lancedb_storage:
|
|
184
|
+
try:
|
|
185
|
+
results = self.lancedb_storage.search_interactions(
|
|
186
|
+
query, user_id, start_date, end_date
|
|
187
|
+
)
|
|
188
|
+
logger.debug(f"Found {len(results)} interactions via LanceDB search")
|
|
189
|
+
return results
|
|
190
|
+
except Exception as e:
|
|
191
|
+
logger.error(f"LanceDB search failed: {e}")
|
|
192
|
+
|
|
193
|
+
# Fallback to markdown search
|
|
194
|
+
if self.markdown_storage:
|
|
195
|
+
try:
|
|
196
|
+
results = self.markdown_storage.search_interactions(
|
|
197
|
+
query, user_id, start_date, end_date
|
|
198
|
+
)
|
|
199
|
+
logger.debug(f"Found {len(results)} interactions via markdown search")
|
|
200
|
+
return results
|
|
201
|
+
except Exception as e:
|
|
202
|
+
logger.error(f"Markdown search failed: {e}")
|
|
203
|
+
|
|
204
|
+
return []
|
|
205
|
+
|
|
206
|
+
def save_memory_component(self, component_name: str, component_data: Any) -> None:
|
|
207
|
+
"""Save memory component (core, semantic, working, episodic) for backup"""
|
|
208
|
+
if not self.is_enabled():
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
# Save to markdown as human-readable snapshot
|
|
212
|
+
if self.markdown_storage and hasattr(self.markdown_storage, 'save_memory_component'):
|
|
213
|
+
try:
|
|
214
|
+
self.markdown_storage.save_memory_component(component_name, component_data)
|
|
215
|
+
logger.debug(f"Saved {component_name} component to markdown storage")
|
|
216
|
+
except Exception as e:
|
|
217
|
+
logger.error(f"Failed to save {component_name} to markdown: {e}")
|
|
218
|
+
|
|
219
|
+
# Save to LanceDB for efficient querying
|
|
220
|
+
if self.lancedb_storage and hasattr(self.lancedb_storage, 'save_memory_component'):
|
|
221
|
+
try:
|
|
222
|
+
self.lancedb_storage.save_memory_component(component_name, component_data)
|
|
223
|
+
logger.debug(f"Saved {component_name} component to LanceDB storage")
|
|
224
|
+
except Exception as e:
|
|
225
|
+
logger.error(f"Failed to save {component_name} to LanceDB: {e}")
|
|
226
|
+
|
|
227
|
+
def load_memory_component(self, component_name: str) -> Optional[Any]:
|
|
228
|
+
"""Load memory component. Prefers LanceDB for performance."""
|
|
229
|
+
if not self.is_enabled():
|
|
230
|
+
return None
|
|
231
|
+
|
|
232
|
+
# Try LanceDB first for performance
|
|
233
|
+
if self.lancedb_storage and hasattr(self.lancedb_storage, 'load_memory_component'):
|
|
234
|
+
try:
|
|
235
|
+
component = self.lancedb_storage.load_memory_component(component_name)
|
|
236
|
+
if component is not None:
|
|
237
|
+
logger.debug(f"Loaded {component_name} component from LanceDB storage")
|
|
238
|
+
return component
|
|
239
|
+
except Exception as e:
|
|
240
|
+
logger.error(f"Failed to load {component_name} from LanceDB: {e}")
|
|
241
|
+
|
|
242
|
+
# Fallback to markdown
|
|
243
|
+
if self.markdown_storage and hasattr(self.markdown_storage, 'load_memory_component'):
|
|
244
|
+
try:
|
|
245
|
+
component = self.markdown_storage.load_memory_component(component_name)
|
|
246
|
+
if component is not None:
|
|
247
|
+
logger.debug(f"Loaded {component_name} component from markdown storage")
|
|
248
|
+
return component
|
|
249
|
+
except Exception as e:
|
|
250
|
+
logger.error(f"Failed to load {component_name} from markdown: {e}")
|
|
251
|
+
|
|
252
|
+
return None
|
|
253
|
+
|
|
254
|
+
def get_storage_stats(self) -> Dict[str, Any]:
|
|
255
|
+
"""Get statistics about stored data"""
|
|
256
|
+
stats = {
|
|
257
|
+
"mode": self.mode,
|
|
258
|
+
"markdown_enabled": self.markdown_storage is not None,
|
|
259
|
+
"lancedb_enabled": self.lancedb_storage is not None,
|
|
260
|
+
"embedding_provider": self.embedding_provider is not None
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if self.markdown_storage and hasattr(self.markdown_storage, 'get_stats'):
|
|
264
|
+
stats["markdown_stats"] = self.markdown_storage.get_stats()
|
|
265
|
+
|
|
266
|
+
if self.lancedb_storage and hasattr(self.lancedb_storage, 'get_stats'):
|
|
267
|
+
stats["lancedb_stats"] = self.lancedb_storage.get_stats()
|
|
268
|
+
|
|
269
|
+
return stats
|