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.
@@ -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