AbstractMemory 0.1.0__py3-none-any.whl → 0.2.2__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 +3 -3
- abstractmemory/embeddings/__init__.py +317 -0
- abstractmemory/storage/dual_manager.py +2 -11
- abstractmemory/storage/lancedb_storage.py +152 -33
- {abstractmemory-0.1.0.dist-info → abstractmemory-0.2.2.dist-info}/METADATA +162 -33
- {abstractmemory-0.1.0.dist-info → abstractmemory-0.2.2.dist-info}/RECORD +9 -8
- {abstractmemory-0.1.0.dist-info → abstractmemory-0.2.2.dist-info}/WHEEL +0 -0
- {abstractmemory-0.1.0.dist-info → abstractmemory-0.2.2.dist-info}/licenses/LICENSE +0 -0
- {abstractmemory-0.1.0.dist-info → abstractmemory-0.2.2.dist-info}/top_level.txt +0 -0
abstractmemory/__init__.py
CHANGED
|
@@ -133,7 +133,7 @@ class GroundedMemory:
|
|
|
133
133
|
)
|
|
134
134
|
|
|
135
135
|
# Legacy storage backend for compatibility
|
|
136
|
-
self.storage = self._init_storage(storage_backend)
|
|
136
|
+
self.storage = self._init_storage(storage_backend, embedding_provider)
|
|
137
137
|
|
|
138
138
|
def set_current_user(self, user_id: str, relationship: Optional[str] = None):
|
|
139
139
|
"""Set the current user for relational context"""
|
|
@@ -545,12 +545,12 @@ class GroundedMemory:
|
|
|
545
545
|
logging.warning(f"Failed to initialize storage manager: {e}")
|
|
546
546
|
return None
|
|
547
547
|
|
|
548
|
-
def _init_storage(self, backend: Optional[str]):
|
|
548
|
+
def _init_storage(self, backend: Optional[str], embedding_provider: Optional[Any] = None):
|
|
549
549
|
"""Initialize storage backend (legacy compatibility)"""
|
|
550
550
|
if backend == 'lancedb':
|
|
551
551
|
try:
|
|
552
552
|
from .storage.lancedb_storage import LanceDBStorage
|
|
553
|
-
return LanceDBStorage("./lance.db")
|
|
553
|
+
return LanceDBStorage("./lance.db", embedding_provider)
|
|
554
554
|
except ImportError:
|
|
555
555
|
return None
|
|
556
556
|
elif backend == 'file':
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Real embedding providers for semantic search capabilities.
|
|
3
|
+
|
|
4
|
+
Provides a unified interface for generating embeddings from real providers:
|
|
5
|
+
AbstractCore EmbeddingManager, OpenAI, Ollama with semantic capabilities.
|
|
6
|
+
|
|
7
|
+
NO FALLBACKS - only real semantic embedding providers are supported.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import hashlib
|
|
12
|
+
from typing import List, Optional, Any, Union
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class EmbeddingAdapter:
|
|
19
|
+
"""
|
|
20
|
+
Unified embedding interface for various providers.
|
|
21
|
+
|
|
22
|
+
IMPORTANT: The embedding provider/model must remain consistent within a storage space.
|
|
23
|
+
You can change LLM providers freely, but changing embedding models requires recreating
|
|
24
|
+
your vector database as different models produce incompatible vector spaces.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, provider: Optional[Any] = None):
|
|
28
|
+
"""
|
|
29
|
+
Initialize embedding adapter.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
provider: Embedding provider (AbstractCore EmbeddingManager, OpenAI client, etc.)
|
|
33
|
+
Note: This is for EMBEDDINGS only, not LLM text generation.
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
ValueError: If no embedding provider is available
|
|
37
|
+
"""
|
|
38
|
+
if provider is None:
|
|
39
|
+
raise ValueError(
|
|
40
|
+
"AbstractMemory semantic search requires a dedicated embedding provider. "
|
|
41
|
+
"This is separate from your LLM provider for text generation. "
|
|
42
|
+
"Please provide: AbstractCore EmbeddingManager, OpenAI client, Ollama with embeddings, etc."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
self.provider = provider
|
|
46
|
+
self.provider_type = self._detect_provider_type()
|
|
47
|
+
self.embedding_dimension = self._get_embedding_dimension()
|
|
48
|
+
self.model_info = self._get_model_info()
|
|
49
|
+
|
|
50
|
+
def _detect_provider_type(self) -> str:
|
|
51
|
+
"""Detect the type of provider and its embedding capabilities."""
|
|
52
|
+
# Check for AbstractCore EmbeddingManager (preferred)
|
|
53
|
+
try:
|
|
54
|
+
from abstractllm.embeddings import EmbeddingManager
|
|
55
|
+
if isinstance(self.provider, EmbeddingManager):
|
|
56
|
+
return "abstractcore_embeddings"
|
|
57
|
+
except ImportError:
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
# Check for AbstractCore provider with embedding support (has specific AbstractCore attributes)
|
|
61
|
+
if hasattr(self.provider, 'generate_embedding') and hasattr(self.provider, 'provider_name'):
|
|
62
|
+
return "abstractcore"
|
|
63
|
+
|
|
64
|
+
# Check for OpenAI client
|
|
65
|
+
if hasattr(self.provider, 'embeddings'):
|
|
66
|
+
return "openai"
|
|
67
|
+
|
|
68
|
+
# Check for provider name attribute (Ollama, MLX, etc.)
|
|
69
|
+
if hasattr(self.provider, 'provider_name'):
|
|
70
|
+
provider_name = getattr(self.provider, 'provider_name', '').lower()
|
|
71
|
+
if 'ollama' in provider_name:
|
|
72
|
+
return "ollama"
|
|
73
|
+
elif 'mlx' in provider_name:
|
|
74
|
+
return "mlx"
|
|
75
|
+
|
|
76
|
+
# Check if provider has generate_embedding method (generic embedding provider)
|
|
77
|
+
if hasattr(self.provider, 'generate_embedding') and callable(getattr(self.provider, 'generate_embedding')):
|
|
78
|
+
return "generic_embedding_provider"
|
|
79
|
+
|
|
80
|
+
# If we can't identify an embedding provider, raise an error
|
|
81
|
+
raise ValueError(
|
|
82
|
+
f"Unable to identify an embedding provider from: {type(self.provider)}. "
|
|
83
|
+
"Supported providers: AbstractCore EmbeddingManager, OpenAI client, "
|
|
84
|
+
"Ollama with embeddings, or any object with 'generate_embedding()' method."
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def _get_embedding_dimension(self) -> int:
|
|
88
|
+
"""Get the embedding dimension based on provider type."""
|
|
89
|
+
if self.provider_type == "abstractcore_embeddings":
|
|
90
|
+
# Get dimension from a test embedding
|
|
91
|
+
try:
|
|
92
|
+
test_embedding = self.provider.embed("dimension_test")
|
|
93
|
+
return len(test_embedding)
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logger.error(f"Failed to get embedding dimension from AbstractCore: {e}")
|
|
96
|
+
raise ValueError("Unable to determine embedding dimension from AbstractCore provider")
|
|
97
|
+
elif self.provider_type == "openai":
|
|
98
|
+
return 1536 # text-embedding-3-small default
|
|
99
|
+
elif self.provider_type == "ollama":
|
|
100
|
+
# Try to get dimension from test embedding
|
|
101
|
+
try:
|
|
102
|
+
test_embedding = self._generate_ollama_embedding("dimension_test")
|
|
103
|
+
return len(test_embedding)
|
|
104
|
+
except:
|
|
105
|
+
return 1024 # Common Ollama embedding dimension
|
|
106
|
+
elif self.provider_type == "generic_embedding_provider":
|
|
107
|
+
# For any provider with generate_embedding method
|
|
108
|
+
try:
|
|
109
|
+
test_embedding = self.provider.generate_embedding("dimension_test")
|
|
110
|
+
return len(test_embedding)
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logger.error(f"Failed to determine embedding dimension from generic provider: {e}")
|
|
113
|
+
raise ValueError(f"Unable to determine embedding dimension: {e}")
|
|
114
|
+
else:
|
|
115
|
+
# For any other provider, attempt to generate a test embedding
|
|
116
|
+
try:
|
|
117
|
+
test_embedding = self.generate_embedding("dimension_test")
|
|
118
|
+
return len(test_embedding)
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.error(f"Failed to determine embedding dimension: {e}")
|
|
121
|
+
raise ValueError(f"Unable to determine embedding dimension for provider type: {self.provider_type}")
|
|
122
|
+
|
|
123
|
+
def _get_model_info(self) -> dict:
|
|
124
|
+
"""Get detailed information about the embedding model for consistency tracking."""
|
|
125
|
+
info = {
|
|
126
|
+
"provider_type": self.provider_type,
|
|
127
|
+
"dimension": self.embedding_dimension,
|
|
128
|
+
"created_at": datetime.now().isoformat()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if self.provider_type == "abstractcore_embeddings":
|
|
132
|
+
# Try to get model name from AbstractCore - only store serializable strings
|
|
133
|
+
try:
|
|
134
|
+
if hasattr(self.provider, 'model'):
|
|
135
|
+
model_attr = getattr(self.provider, 'model')
|
|
136
|
+
if isinstance(model_attr, str):
|
|
137
|
+
info["model_name"] = model_attr
|
|
138
|
+
else:
|
|
139
|
+
# Get string representation of the model
|
|
140
|
+
info["model_name"] = str(type(model_attr).__name__)
|
|
141
|
+
if hasattr(self.provider, 'backend'):
|
|
142
|
+
info["backend"] = str(self.provider.backend)
|
|
143
|
+
except Exception as e:
|
|
144
|
+
logger.debug(f"Could not extract model info: {e}")
|
|
145
|
+
elif self.provider_type == "openai":
|
|
146
|
+
info["model_name"] = "text-embedding-3-small" # Default assumption
|
|
147
|
+
|
|
148
|
+
return info
|
|
149
|
+
|
|
150
|
+
def generate_embedding(self, text: str) -> List[float]:
|
|
151
|
+
"""
|
|
152
|
+
Generate embedding for the given text using the configured provider.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
text: Input text to embed
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
List[float]: Embedding vector
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
EmbeddingError: If embedding generation fails
|
|
162
|
+
"""
|
|
163
|
+
try:
|
|
164
|
+
if self.provider_type == "abstractcore_embeddings":
|
|
165
|
+
return self._generate_abstractcore_embeddings(text)
|
|
166
|
+
elif self.provider_type == "abstractcore":
|
|
167
|
+
return self._generate_abstractcore_embedding(text)
|
|
168
|
+
elif self.provider_type == "openai":
|
|
169
|
+
return self._generate_openai_embedding(text)
|
|
170
|
+
elif self.provider_type == "ollama":
|
|
171
|
+
return self._generate_ollama_embedding(text)
|
|
172
|
+
elif self.provider_type == "mlx":
|
|
173
|
+
return self._generate_mlx_embedding(text)
|
|
174
|
+
elif self.provider_type == "generic_embedding_provider":
|
|
175
|
+
return self.provider.generate_embedding(text)
|
|
176
|
+
else:
|
|
177
|
+
raise EmbeddingError(f"Unknown provider type: {self.provider_type}")
|
|
178
|
+
|
|
179
|
+
except Exception as e:
|
|
180
|
+
logger.error(f"Embedding generation failed with {self.provider_type}: {e}")
|
|
181
|
+
raise EmbeddingError(f"Failed to generate embedding: {e}") from e
|
|
182
|
+
|
|
183
|
+
def _generate_abstractcore_embeddings(self, text: str) -> List[float]:
|
|
184
|
+
"""Generate embedding using AbstractCore EmbeddingManager."""
|
|
185
|
+
return self.provider.embed(text)
|
|
186
|
+
|
|
187
|
+
def _generate_abstractcore_embedding(self, text: str) -> List[float]:
|
|
188
|
+
"""Generate embedding using AbstractCore provider."""
|
|
189
|
+
return self.provider.generate_embedding(text)
|
|
190
|
+
|
|
191
|
+
def _generate_openai_embedding(self, text: str) -> List[float]:
|
|
192
|
+
"""Generate embedding using OpenAI client."""
|
|
193
|
+
response = self.provider.embeddings.create(
|
|
194
|
+
model="text-embedding-3-small",
|
|
195
|
+
input=text
|
|
196
|
+
)
|
|
197
|
+
return response.data[0].embedding
|
|
198
|
+
|
|
199
|
+
def _generate_ollama_embedding(self, text: str) -> List[float]:
|
|
200
|
+
"""Generate embedding using Ollama provider."""
|
|
201
|
+
try:
|
|
202
|
+
import requests
|
|
203
|
+
response = requests.post(
|
|
204
|
+
"http://localhost:11434/api/embeddings",
|
|
205
|
+
json={
|
|
206
|
+
"model": "nomic-embed-text",
|
|
207
|
+
"prompt": text
|
|
208
|
+
},
|
|
209
|
+
timeout=30
|
|
210
|
+
)
|
|
211
|
+
if response.status_code == 200:
|
|
212
|
+
embedding = response.json()["embedding"]
|
|
213
|
+
if embedding and isinstance(embedding, list):
|
|
214
|
+
return embedding
|
|
215
|
+
raise EmbeddingError(f"Ollama API returned status {response.status_code}")
|
|
216
|
+
except ImportError:
|
|
217
|
+
raise EmbeddingError("requests library not available for Ollama embedding API")
|
|
218
|
+
except Exception as e:
|
|
219
|
+
raise EmbeddingError(f"Ollama embedding generation failed: {e}") from e
|
|
220
|
+
|
|
221
|
+
def _generate_mlx_embedding(self, text: str) -> List[float]:
|
|
222
|
+
"""Generate embedding using MLX provider."""
|
|
223
|
+
# MLX provider should implement actual MLX embedding model
|
|
224
|
+
raise EmbeddingError(
|
|
225
|
+
"MLX embedding implementation not yet available. "
|
|
226
|
+
"Please use AbstractCore EmbeddingManager or another provider."
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
def is_real_embedding(self) -> bool:
|
|
230
|
+
"""Check if this adapter provides real semantic embeddings."""
|
|
231
|
+
return self.provider_type in ["abstractcore_embeddings", "abstractcore", "openai", "ollama", "generic_embedding_provider"]
|
|
232
|
+
|
|
233
|
+
def get_embedding_info(self) -> dict:
|
|
234
|
+
"""Get comprehensive information about the embedding provider for consistency tracking."""
|
|
235
|
+
info = self.model_info.copy()
|
|
236
|
+
info.update({
|
|
237
|
+
"is_real_embedding": self.is_real_embedding(),
|
|
238
|
+
"provider_available": self.provider is not None
|
|
239
|
+
})
|
|
240
|
+
return info
|
|
241
|
+
|
|
242
|
+
def check_consistency_with(self, other_model_info: dict) -> bool:
|
|
243
|
+
"""
|
|
244
|
+
Check if this adapter is consistent with previously stored model info.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
other_model_info: Previously stored model information
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
bool: True if models are compatible for semantic search
|
|
251
|
+
"""
|
|
252
|
+
current_info = self.get_embedding_info()
|
|
253
|
+
|
|
254
|
+
# Check critical compatibility factors
|
|
255
|
+
if current_info.get("provider_type") != other_model_info.get("provider_type"):
|
|
256
|
+
logger.warning(f"Provider type mismatch: {current_info.get('provider_type')} vs {other_model_info.get('provider_type')}")
|
|
257
|
+
return False
|
|
258
|
+
|
|
259
|
+
if current_info.get("dimension") != other_model_info.get("dimension"):
|
|
260
|
+
logger.warning(f"Dimension mismatch: {current_info.get('dimension')} vs {other_model_info.get('dimension')}")
|
|
261
|
+
return False
|
|
262
|
+
|
|
263
|
+
if current_info.get("model_name") != other_model_info.get("model_name"):
|
|
264
|
+
logger.warning(f"Model name mismatch: {current_info.get('model_name')} vs {other_model_info.get('model_name')}")
|
|
265
|
+
return False
|
|
266
|
+
|
|
267
|
+
return True
|
|
268
|
+
|
|
269
|
+
def warn_about_consistency(self, stored_model_info: dict) -> None:
|
|
270
|
+
"""
|
|
271
|
+
Issue warnings about embedding model consistency issues.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
stored_model_info: Information about previously stored embeddings
|
|
275
|
+
"""
|
|
276
|
+
if not self.check_consistency_with(stored_model_info):
|
|
277
|
+
current_info = self.get_embedding_info()
|
|
278
|
+
logger.warning(
|
|
279
|
+
"\n" + "="*80 + "\n"
|
|
280
|
+
"🚨 CRITICAL: EMBEDDING MODEL INCONSISTENCY DETECTED 🚨\n"
|
|
281
|
+
"="*80 + "\n"
|
|
282
|
+
"You are attempting to use a different embedding model than what was\n"
|
|
283
|
+
"previously used in this storage space. This BREAKS semantic search!\n\n"
|
|
284
|
+
f"CURRENT embedding model:\n"
|
|
285
|
+
f" • Provider: {current_info.get('provider_type', 'Unknown')}\n"
|
|
286
|
+
f" • Model: {current_info.get('model_name', 'Unknown')}\n"
|
|
287
|
+
f" • Dimensions: {current_info.get('dimension', 'Unknown')}\n\n"
|
|
288
|
+
f"STORED embedding model:\n"
|
|
289
|
+
f" • Provider: {stored_model_info.get('provider_type', 'Unknown')}\n"
|
|
290
|
+
f" • Model: {stored_model_info.get('model_name', 'Unknown')}\n"
|
|
291
|
+
f" • Dimensions: {stored_model_info.get('dimension', 'Unknown')}\n\n"
|
|
292
|
+
"IMPORTANT: You can change LLM providers freely (Anthropic ↔ OpenAI ↔ Ollama)\n"
|
|
293
|
+
"but embedding models must remain consistent within a storage space.\n\n"
|
|
294
|
+
"TO FIX THIS ISSUE:\n"
|
|
295
|
+
"1. Use the SAME embedding model as stored, OR\n"
|
|
296
|
+
"2. Delete your vector database and start fresh with the new model\n"
|
|
297
|
+
" (this will re-embed all interactions with the new model)\n"
|
|
298
|
+
"="*80
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
class EmbeddingError(Exception):
|
|
303
|
+
"""Base exception for embedding-related errors."""
|
|
304
|
+
pass
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def create_embedding_adapter(provider: Optional[Any] = None) -> EmbeddingAdapter:
|
|
308
|
+
"""
|
|
309
|
+
Create an embedding adapter for the given provider.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
provider: LLM provider instance
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
EmbeddingAdapter: Configured adapter
|
|
316
|
+
"""
|
|
317
|
+
return EmbeddingAdapter(provider)
|
|
@@ -93,12 +93,7 @@ class DualStorageManager:
|
|
|
93
93
|
# Save to LanceDB storage
|
|
94
94
|
if self.lancedb_storage:
|
|
95
95
|
try:
|
|
96
|
-
#
|
|
97
|
-
embedding = None
|
|
98
|
-
if self.embedding_provider:
|
|
99
|
-
text = f"{user_input} {agent_response}"
|
|
100
|
-
embedding = self.embedding_provider.generate_embedding(text)
|
|
101
|
-
|
|
96
|
+
# LanceDB storage handles embedding generation internally
|
|
102
97
|
ldb_id = self.lancedb_storage.save_interaction(
|
|
103
98
|
user_id, timestamp, user_input, agent_response, topic, metadata
|
|
104
99
|
)
|
|
@@ -138,11 +133,7 @@ class DualStorageManager:
|
|
|
138
133
|
# Save to LanceDB storage
|
|
139
134
|
if self.lancedb_storage:
|
|
140
135
|
try:
|
|
141
|
-
#
|
|
142
|
-
embedding = None
|
|
143
|
-
if self.embedding_provider:
|
|
144
|
-
embedding = self.embedding_provider.generate_embedding(reflection)
|
|
145
|
-
|
|
136
|
+
# LanceDB storage handles embedding generation internally
|
|
146
137
|
ldb_note_id = self.lancedb_storage.save_experiential_note(
|
|
147
138
|
timestamp, reflection, interaction_id, note_type, metadata
|
|
148
139
|
)
|
|
@@ -9,6 +9,7 @@ from datetime import datetime
|
|
|
9
9
|
import logging
|
|
10
10
|
|
|
11
11
|
from ..core.interfaces import IStorage
|
|
12
|
+
from ..embeddings import create_embedding_adapter
|
|
12
13
|
|
|
13
14
|
logger = logging.getLogger(__name__)
|
|
14
15
|
|
|
@@ -38,16 +39,27 @@ class LanceDBStorage(IStorage):
|
|
|
38
39
|
Args:
|
|
39
40
|
uri: LanceDB connection URI (e.g., "./lance.db")
|
|
40
41
|
embedding_provider: AbstractCore instance for generating embeddings
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
ImportError: If LanceDB is not installed
|
|
45
|
+
ValueError: If no embedding provider is provided
|
|
41
46
|
"""
|
|
42
47
|
if not LANCEDB_AVAILABLE:
|
|
43
48
|
raise ImportError("LanceDB is required but not installed. Install with: pip install lancedb")
|
|
44
49
|
|
|
50
|
+
if embedding_provider is None:
|
|
51
|
+
raise ValueError(
|
|
52
|
+
"LanceDB storage requires a real embedding provider for semantic search. "
|
|
53
|
+
"Please provide an AbstractCore EmbeddingManager or other embedding provider."
|
|
54
|
+
)
|
|
55
|
+
|
|
45
56
|
self.uri = uri
|
|
46
|
-
self.
|
|
57
|
+
self.embedding_adapter = create_embedding_adapter(embedding_provider)
|
|
47
58
|
self.db = lancedb.connect(uri)
|
|
48
59
|
|
|
49
|
-
# Initialize tables
|
|
60
|
+
# Initialize tables and check embedding consistency
|
|
50
61
|
self._init_tables()
|
|
62
|
+
self._check_embedding_consistency()
|
|
51
63
|
|
|
52
64
|
def _init_tables(self):
|
|
53
65
|
"""Initialize LanceDB tables with schemas"""
|
|
@@ -92,43 +104,136 @@ class LanceDBStorage(IStorage):
|
|
|
92
104
|
]
|
|
93
105
|
|
|
94
106
|
# Create tables if they don't exist
|
|
107
|
+
import pandas as pd
|
|
108
|
+
|
|
95
109
|
try:
|
|
96
110
|
self.interactions_table = self.db.open_table("interactions")
|
|
97
|
-
except FileNotFoundError:
|
|
98
|
-
# Create
|
|
99
|
-
import
|
|
100
|
-
|
|
101
|
-
|
|
111
|
+
except (FileNotFoundError, ValueError):
|
|
112
|
+
# Create table with proper schema and sample data
|
|
113
|
+
import pyarrow as pa
|
|
114
|
+
|
|
115
|
+
# Get actual embedding dimension from adapter
|
|
116
|
+
test_embedding = self.embedding_adapter.generate_embedding("test")
|
|
117
|
+
embedding_dim = len(test_embedding)
|
|
118
|
+
|
|
119
|
+
# Create proper schema with fixed-size list for embeddings
|
|
120
|
+
schema = pa.schema([
|
|
121
|
+
pa.field("id", pa.string()),
|
|
122
|
+
pa.field("user_id", pa.string()),
|
|
123
|
+
pa.field("timestamp", pa.timestamp('us')),
|
|
124
|
+
pa.field("user_input", pa.string()),
|
|
125
|
+
pa.field("agent_response", pa.string()),
|
|
126
|
+
pa.field("topic", pa.string()),
|
|
127
|
+
pa.field("metadata", pa.string()),
|
|
128
|
+
pa.field("embedding", pa.list_(pa.float32(), embedding_dim))
|
|
129
|
+
])
|
|
130
|
+
|
|
131
|
+
# Create empty table with proper schema
|
|
132
|
+
self.interactions_table = self.db.create_table("interactions", schema=schema)
|
|
102
133
|
|
|
103
134
|
try:
|
|
104
135
|
self.notes_table = self.db.open_table("experiential_notes")
|
|
105
|
-
except FileNotFoundError:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
136
|
+
except (FileNotFoundError, ValueError):
|
|
137
|
+
# Create notes table with proper schema
|
|
138
|
+
notes_schema = pa.schema([
|
|
139
|
+
pa.field("id", pa.string()),
|
|
140
|
+
pa.field("timestamp", pa.timestamp('us')),
|
|
141
|
+
pa.field("reflection", pa.string()),
|
|
142
|
+
pa.field("interaction_id", pa.string()),
|
|
143
|
+
pa.field("note_type", pa.string()),
|
|
144
|
+
pa.field("metadata", pa.string()),
|
|
145
|
+
pa.field("embedding", pa.list_(pa.float32(), embedding_dim))
|
|
146
|
+
])
|
|
147
|
+
self.notes_table = self.db.create_table("experiential_notes", schema=notes_schema)
|
|
109
148
|
|
|
110
149
|
try:
|
|
111
150
|
self.links_table = self.db.open_table("links")
|
|
112
|
-
except FileNotFoundError:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
151
|
+
except (FileNotFoundError, ValueError):
|
|
152
|
+
sample_data = pd.DataFrame([{
|
|
153
|
+
"interaction_id": "sample_int",
|
|
154
|
+
"note_id": "sample_note",
|
|
155
|
+
"created": datetime.now(),
|
|
156
|
+
"link_type": "bidirectional"
|
|
157
|
+
}])
|
|
158
|
+
self.links_table = self.db.create_table("links", sample_data)
|
|
159
|
+
self.links_table.delete("interaction_id = 'sample_int'")
|
|
116
160
|
|
|
117
161
|
try:
|
|
118
162
|
self.components_table = self.db.open_table("memory_components")
|
|
119
|
-
except FileNotFoundError:
|
|
163
|
+
except (FileNotFoundError, ValueError):
|
|
164
|
+
sample_data = pd.DataFrame([{
|
|
165
|
+
"component_name": "sample",
|
|
166
|
+
"timestamp": datetime.now(),
|
|
167
|
+
"data": "{}",
|
|
168
|
+
"version": 1
|
|
169
|
+
}])
|
|
170
|
+
self.components_table = self.db.create_table("memory_components", sample_data)
|
|
171
|
+
self.components_table.delete("component_name = 'sample'")
|
|
172
|
+
|
|
173
|
+
# Embedding metadata table for consistency tracking
|
|
174
|
+
try:
|
|
175
|
+
self.embedding_metadata_table = self.db.open_table("embedding_metadata")
|
|
176
|
+
except (FileNotFoundError, ValueError):
|
|
177
|
+
sample_data = pd.DataFrame([{
|
|
178
|
+
"key": "sample",
|
|
179
|
+
"value": "{}",
|
|
180
|
+
"created_at": datetime.now()
|
|
181
|
+
}])
|
|
182
|
+
self.embedding_metadata_table = self.db.create_table("embedding_metadata", sample_data)
|
|
183
|
+
self.embedding_metadata_table.delete("key = 'sample'")
|
|
184
|
+
|
|
185
|
+
def _check_embedding_consistency(self) -> None:
|
|
186
|
+
"""Check for embedding model consistency with previously stored data."""
|
|
187
|
+
try:
|
|
188
|
+
# Get current embedding model info
|
|
189
|
+
current_info = self.embedding_adapter.get_embedding_info()
|
|
190
|
+
|
|
191
|
+
# Try to retrieve previously stored embedding info
|
|
192
|
+
stored_info_df = self.embedding_metadata_table.search().where("key = 'embedding_model_info'").to_pandas()
|
|
193
|
+
|
|
194
|
+
if len(stored_info_df) > 0:
|
|
195
|
+
# We have previously stored embedding info
|
|
196
|
+
import json
|
|
197
|
+
stored_info = json.loads(stored_info_df.iloc[0]['value'])
|
|
198
|
+
|
|
199
|
+
# Check consistency and warn if needed
|
|
200
|
+
self.embedding_adapter.warn_about_consistency(stored_info)
|
|
201
|
+
else:
|
|
202
|
+
# First time - store the current embedding info
|
|
203
|
+
self._store_embedding_info(current_info)
|
|
204
|
+
logger.info(f"Stored embedding model info for consistency tracking: {current_info}")
|
|
205
|
+
|
|
206
|
+
except Exception as e:
|
|
207
|
+
logger.warning(f"Failed to check embedding consistency: {e}")
|
|
208
|
+
|
|
209
|
+
def _store_embedding_info(self, embedding_info: dict) -> None:
|
|
210
|
+
"""Store embedding model information for consistency tracking."""
|
|
211
|
+
try:
|
|
212
|
+
import json
|
|
120
213
|
import pandas as pd
|
|
121
|
-
empty_df = pd.DataFrame(columns=[col["name"] for col in components_schema])
|
|
122
|
-
self.components_table = self.db.create_table("memory_components", empty_df)
|
|
123
214
|
|
|
124
|
-
|
|
125
|
-
"""Generate embedding using AbstractCore provider"""
|
|
126
|
-
if self.embedding_provider and hasattr(self.embedding_provider, 'generate_embedding'):
|
|
215
|
+
# Delete any existing embedding_model_info records
|
|
127
216
|
try:
|
|
128
|
-
|
|
129
|
-
except
|
|
130
|
-
|
|
131
|
-
|
|
217
|
+
self.embedding_metadata_table.delete("key = 'embedding_model_info'")
|
|
218
|
+
except:
|
|
219
|
+
pass # Table might be empty
|
|
220
|
+
|
|
221
|
+
# Store new info
|
|
222
|
+
data = pd.DataFrame([{
|
|
223
|
+
"key": "embedding_model_info",
|
|
224
|
+
"value": json.dumps(embedding_info),
|
|
225
|
+
"created_at": datetime.now()
|
|
226
|
+
}])
|
|
227
|
+
|
|
228
|
+
self.embedding_metadata_table.add(data)
|
|
229
|
+
logger.debug(f"Stored embedding model info: {embedding_info}")
|
|
230
|
+
|
|
231
|
+
except Exception as e:
|
|
232
|
+
logger.error(f"Failed to store embedding info: {e}")
|
|
233
|
+
|
|
234
|
+
def _generate_embedding(self, text: str) -> List[float]:
|
|
235
|
+
"""Generate embedding using embedding adapter"""
|
|
236
|
+
return self.embedding_adapter.generate_embedding(text)
|
|
132
237
|
|
|
133
238
|
def save_interaction(self, user_id: str, timestamp: datetime,
|
|
134
239
|
user_input: str, agent_response: str,
|
|
@@ -153,7 +258,7 @@ class LanceDBStorage(IStorage):
|
|
|
153
258
|
"agent_response": agent_response,
|
|
154
259
|
"topic": topic,
|
|
155
260
|
"metadata": json.dumps(metadata or {}),
|
|
156
|
-
"embedding":
|
|
261
|
+
"embedding": [float(x) for x in embedding] # Ensure float32 compatibility
|
|
157
262
|
}
|
|
158
263
|
|
|
159
264
|
# Insert into table
|
|
@@ -189,7 +294,7 @@ class LanceDBStorage(IStorage):
|
|
|
189
294
|
"interaction_id": interaction_id,
|
|
190
295
|
"note_type": note_type,
|
|
191
296
|
"metadata": json.dumps(metadata or {}),
|
|
192
|
-
"embedding":
|
|
297
|
+
"embedding": [float(x) for x in embedding] # Ensure float32 compatibility
|
|
193
298
|
}
|
|
194
299
|
|
|
195
300
|
# Insert into table
|
|
@@ -253,13 +358,13 @@ class LanceDBStorage(IStorage):
|
|
|
253
358
|
# Build WHERE clause
|
|
254
359
|
where_clause = " AND ".join(query_parts) if query_parts else None
|
|
255
360
|
|
|
256
|
-
# Try vector search first if embedding
|
|
257
|
-
if self.
|
|
361
|
+
# Try vector search first if embedding adapter available
|
|
362
|
+
if self.embedding_adapter:
|
|
258
363
|
try:
|
|
259
364
|
query_embedding = self._generate_embedding(query)
|
|
260
365
|
if query_embedding:
|
|
261
366
|
# Vector similarity search
|
|
262
|
-
results = self.interactions_table.search(query_embedding).limit(50)
|
|
367
|
+
results = self.interactions_table.search(query_embedding, vector_column_name="embedding").limit(50)
|
|
263
368
|
|
|
264
369
|
# Apply additional filters
|
|
265
370
|
if where_clause:
|
|
@@ -408,18 +513,32 @@ class LanceDBStorage(IStorage):
|
|
|
408
513
|
links_count = len(self.links_table.search().limit(10000).to_pandas())
|
|
409
514
|
components_count = len(self.components_table.search().limit(1000).to_pandas())
|
|
410
515
|
|
|
411
|
-
|
|
516
|
+
stats = {
|
|
412
517
|
"total_interactions": interactions_count,
|
|
413
518
|
"total_notes": notes_count,
|
|
414
519
|
"total_links": links_count,
|
|
415
520
|
"total_components": components_count,
|
|
416
521
|
"uri": self.uri,
|
|
417
|
-
"embedding_provider_available": self.
|
|
522
|
+
"embedding_provider_available": self.embedding_adapter is not None,
|
|
523
|
+
"embedding_info": self.embedding_adapter.get_embedding_info() if self.embedding_adapter else None
|
|
418
524
|
}
|
|
525
|
+
|
|
526
|
+
# Add stored embedding model info for comparison
|
|
527
|
+
try:
|
|
528
|
+
stored_info_df = self.embedding_metadata_table.search().where("key = 'embedding_model_info'").to_pandas()
|
|
529
|
+
if len(stored_info_df) > 0:
|
|
530
|
+
import json
|
|
531
|
+
stats["stored_embedding_info"] = json.loads(stored_info_df.iloc[0]['value'])
|
|
532
|
+
stats["embedding_consistency"] = self.embedding_adapter.check_consistency_with(stats["stored_embedding_info"]) if self.embedding_adapter else False
|
|
533
|
+
except Exception as e:
|
|
534
|
+
logger.debug(f"Could not retrieve stored embedding info: {e}")
|
|
535
|
+
|
|
536
|
+
return stats
|
|
419
537
|
except Exception as e:
|
|
420
538
|
logger.error(f"Failed to get stats: {e}")
|
|
421
539
|
return {
|
|
422
540
|
"error": str(e),
|
|
423
541
|
"uri": self.uri,
|
|
424
|
-
"embedding_provider_available": self.
|
|
542
|
+
"embedding_provider_available": self.embedding_adapter is not None,
|
|
543
|
+
"embedding_info": self.embedding_adapter.get_embedding_info() if self.embedding_adapter else None
|
|
425
544
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: AbstractMemory
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: Production-ready memory system for LLM agents - NO MOCKS, real semantic search, clear LLM vs embedding provider separation
|
|
5
5
|
Author-email: AbstractMemory Team <lpalbou@gmail.com>
|
|
6
6
|
Maintainer-email: AbstractMemory Team <palbou@gmail.com>
|
|
7
7
|
License-Expression: MIT
|
|
8
|
-
Project-URL: Homepage, https://github.com/lpalbou/
|
|
9
|
-
Project-URL: Documentation, https://github.com/lpalbou/
|
|
10
|
-
Project-URL: Repository, https://github.com/lpalbou/
|
|
11
|
-
Project-URL: Bug Reports, https://github.com/lpalbou/
|
|
12
|
-
Keywords: llm,memory,
|
|
8
|
+
Project-URL: Homepage, https://github.com/lpalbou/AbstractMemory
|
|
9
|
+
Project-URL: Documentation, https://github.com/lpalbou/AbstractMemory#readme
|
|
10
|
+
Project-URL: Repository, https://github.com/lpalbou/AbstractMemory
|
|
11
|
+
Project-URL: Bug Reports, https://github.com/lpalbou/AbstractMemory/issues
|
|
12
|
+
Keywords: llm,memory,semantic-search,embeddings,ai,agents,knowledge-graph,temporal,grounded-memory,vector-search
|
|
13
13
|
Classifier: Development Status :: 5 - Production/Stable
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -30,9 +30,15 @@ Requires-Dist: pytest; extra == "dev"
|
|
|
30
30
|
Requires-Dist: black; extra == "dev"
|
|
31
31
|
Requires-Dist: mypy; extra == "dev"
|
|
32
32
|
Provides-Extra: llm
|
|
33
|
-
Requires-Dist:
|
|
33
|
+
Requires-Dist: abstractcore>=2.1.0; extra == "llm"
|
|
34
|
+
Provides-Extra: embeddings
|
|
35
|
+
Requires-Dist: abstractcore>=2.1.0; extra == "embeddings"
|
|
36
|
+
Requires-Dist: lancedb>=0.6.0; extra == "embeddings"
|
|
34
37
|
Provides-Extra: storage
|
|
35
|
-
Requires-Dist: lancedb; extra == "storage"
|
|
38
|
+
Requires-Dist: lancedb>=0.6.0; extra == "storage"
|
|
39
|
+
Provides-Extra: all
|
|
40
|
+
Requires-Dist: abstractcore>=2.1.0; extra == "all"
|
|
41
|
+
Requires-Dist: lancedb>=0.6.0; extra == "all"
|
|
36
42
|
Dynamic: license-file
|
|
37
43
|
|
|
38
44
|
# AbstractMemory
|
|
@@ -199,20 +205,27 @@ memory = create_memory(
|
|
|
199
205
|
|
|
200
206
|
## 📚 Documentation
|
|
201
207
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
- **[
|
|
206
|
-
- **[
|
|
207
|
-
- **[
|
|
208
|
-
- **[
|
|
208
|
+
**👉 [START HERE: Complete Documentation Guide](docs/README.md)**
|
|
209
|
+
|
|
210
|
+
### Core Guides
|
|
211
|
+
- **[🚀 Quick Start](docs/README.md#-start-here)** - Get running in 5 minutes
|
|
212
|
+
- **[🔍 Semantic Search](docs/semantic-search.md)** - Vector embeddings and similarity search
|
|
213
|
+
- **[🧠 Memory Types](docs/memory-types.md)** - ScratchpadMemory, BufferMemory, GroundedMemory
|
|
214
|
+
- **[📊 Performance Guide](docs/semantic-search.md#performance-characteristics)** - Embedding timing and optimization
|
|
215
|
+
|
|
216
|
+
### Advanced Topics
|
|
217
|
+
- **[🏗️ Architecture](docs/architecture.md)** - System design and two-tier strategy
|
|
218
|
+
- **[💾 Storage Systems](docs/storage-systems.md)** - Markdown + LanceDB dual storage
|
|
219
|
+
- **[🎯 Usage Patterns](docs/usage-patterns.md)** - Real-world examples and best practices
|
|
220
|
+
- **[🔗 Integration Guide](docs/integration.md)** - AbstractLLM ecosystem integration
|
|
221
|
+
- **[📖 API Reference](docs/api-reference.md)** - Complete method documentation
|
|
209
222
|
|
|
210
223
|
## 🔬 Key Features
|
|
211
224
|
|
|
212
225
|
### ✅ Purpose-Built Memory Types
|
|
213
|
-
- **ScratchpadMemory**: ReAct thought-action-observation cycles
|
|
214
|
-
- **BufferMemory**: Simple conversation history
|
|
215
|
-
- **GroundedMemory**:
|
|
226
|
+
- **ScratchpadMemory**: ReAct thought-action-observation cycles for task agents
|
|
227
|
+
- **BufferMemory**: Simple conversation history with capacity limits
|
|
228
|
+
- **GroundedMemory**: Four-tier architecture with semantic search and temporal context
|
|
216
229
|
|
|
217
230
|
### ✅ State-of-the-Art Research Integration
|
|
218
231
|
- **MemGPT/Letta Pattern**: Self-editing core memory
|
|
@@ -238,12 +251,19 @@ Core Memory ──→ Semantic Memory ──→ Working Memory ──→ Episodi
|
|
|
238
251
|
- **🔗 Bidirectional Links**: Connect interactions to AI insights
|
|
239
252
|
- **📊 Search Capabilities**: Text-based and semantic similarity search
|
|
240
253
|
|
|
254
|
+
### ✅ Semantic Search with AbstractCore
|
|
255
|
+
- **🎯 Real Embeddings**: Uses AbstractCore's EmbeddingManager with Google's EmbeddingGemma (768D)
|
|
256
|
+
- **⚡ Immediate Indexing**: Embeddings generated instantly during `add_interaction()` (~36ms)
|
|
257
|
+
- **🔍 Vector Similarity**: True semantic search finds contextually relevant content
|
|
258
|
+
- **🗄️ Dual Storage**: Observable markdown files + searchable LanceDB vectors
|
|
259
|
+
- **🎯 Production Ready**: Sub-second search, proven with 200+ real implementation tests
|
|
260
|
+
|
|
241
261
|
## 🧪 Testing & Validation
|
|
242
262
|
|
|
243
|
-
AbstractMemory includes **200+ comprehensive tests**
|
|
263
|
+
AbstractMemory includes **200+ comprehensive tests** using ONLY real implementations:
|
|
244
264
|
|
|
245
265
|
```bash
|
|
246
|
-
# Run all tests
|
|
266
|
+
# Run all tests (NO MOCKS - only real implementations)
|
|
247
267
|
python -m pytest tests/ -v
|
|
248
268
|
|
|
249
269
|
# Run specific test suites
|
|
@@ -255,41 +275,150 @@ python -m pytest tests/integration/ -v # Full system integration
|
|
|
255
275
|
# Test with real LLM providers (requires AbstractCore)
|
|
256
276
|
python -m pytest tests/integration/test_llm_real_usage.py -v
|
|
257
277
|
|
|
258
|
-
# Test comprehensive dual storage
|
|
278
|
+
# Test comprehensive dual storage with real embeddings
|
|
259
279
|
python -m pytest tests/storage/test_dual_storage_comprehensive.py -v
|
|
260
280
|
```
|
|
261
281
|
|
|
282
|
+
**IMPORTANT**: All tests use real implementations:
|
|
283
|
+
- Real embedding providers (AbstractCore EmbeddingManager)
|
|
284
|
+
- Real LLM providers (Anthropic, OpenAI, Ollama via AbstractCore)
|
|
285
|
+
- Real memory components and storage systems
|
|
286
|
+
- NO MOCKS anywhere in the codebase
|
|
287
|
+
|
|
288
|
+
## 🚀 Quick Start
|
|
289
|
+
|
|
290
|
+
### Installation
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
# Install with semantic search capabilities (recommended)
|
|
294
|
+
pip install abstractmemory[embeddings]
|
|
295
|
+
|
|
296
|
+
# Or install everything
|
|
297
|
+
pip install abstractmemory[all]
|
|
298
|
+
|
|
299
|
+
# Basic memory only (no semantic search)
|
|
300
|
+
pip install abstractmemory
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### 📋 Upgrading from v0.1.0?
|
|
304
|
+
|
|
305
|
+
**Version 0.2.0 adds semantic search!** See [Migration Guide](CHANGELOG.md#-migration-guide) for:
|
|
306
|
+
- New AbstractCore dependency (`pip install abstractcore>=2.1.0`)
|
|
307
|
+
- LanceDB schema changes (recreate `.db` files)
|
|
308
|
+
- New `embedding_provider` parameter
|
|
309
|
+
|
|
310
|
+
### ⚠️ Critical: LLM vs Embedding Provider Separation
|
|
311
|
+
|
|
312
|
+
**Understanding the difference between LLM and Embedding providers:**
|
|
313
|
+
|
|
314
|
+
- 🔄 **LLM Providers** (text generation): Change freely between Anthropic, OpenAI, Ollama, etc.
|
|
315
|
+
- 🔒 **Embedding Providers** (semantic search): Must remain consistent within a storage space
|
|
316
|
+
|
|
317
|
+
**For semantic search consistency:**
|
|
318
|
+
- ✅ **Choose ONE embedding model and stick with it per storage space**
|
|
319
|
+
- ✅ **You can customize which embedding model to use (AbstractCore, OpenAI, Ollama, etc.)**
|
|
320
|
+
- ❌ **Don't change embedding models mid-project - it breaks vector search**
|
|
321
|
+
- 🚨 **AbstractMemory automatically warns when embedding model changes detected**
|
|
322
|
+
|
|
323
|
+
**Example of correct separation:**
|
|
324
|
+
```python
|
|
325
|
+
# LLM for text generation (can change anytime)
|
|
326
|
+
llm = create_llm("anthropic") # or "openai", "ollama", etc.
|
|
327
|
+
|
|
328
|
+
# Dedicated embedding provider (must stay consistent)
|
|
329
|
+
embedder = EmbeddingManager() # AbstractCore embeddings
|
|
330
|
+
|
|
331
|
+
memory = create_memory("grounded", embedding_provider=embedder) # NOT llm!
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Basic Usage
|
|
335
|
+
|
|
336
|
+
```python
|
|
337
|
+
from abstractllm.embeddings import EmbeddingManager
|
|
338
|
+
from abstractmemory import create_memory
|
|
339
|
+
|
|
340
|
+
# 1. Create embedding manager for semantic search
|
|
341
|
+
em = EmbeddingManager() # Uses EmbeddingGemma (768D vectors)
|
|
342
|
+
|
|
343
|
+
# 2. Create memory with dual storage
|
|
344
|
+
memory = create_memory(
|
|
345
|
+
"grounded",
|
|
346
|
+
storage_backend="dual", # Markdown + LanceDB
|
|
347
|
+
storage_path="./memory_files", # Observable files
|
|
348
|
+
storage_uri="./memory.db", # Vector search
|
|
349
|
+
embedding_provider=em # Real embeddings
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
# 3. Add interactions (embeddings generated immediately!)
|
|
353
|
+
memory.set_current_user("alice")
|
|
354
|
+
memory.add_interaction(
|
|
355
|
+
"I'm working on machine learning projects",
|
|
356
|
+
"Great! ML has amazing applications in many fields."
|
|
357
|
+
)
|
|
358
|
+
# ↳ Takes ~36ms: embedding generated and stored instantly
|
|
359
|
+
|
|
360
|
+
# 4. Semantic search finds contextually relevant content
|
|
361
|
+
results = memory.search_stored_interactions("artificial intelligence research")
|
|
362
|
+
# ↳ Finds ML interaction via semantic similarity (not keywords!)
|
|
363
|
+
print(f"Found {len(results)} relevant conversations")
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### 📋 What Happens When You Add Interactions
|
|
367
|
+
|
|
368
|
+
```python
|
|
369
|
+
memory.add_interaction("I love Python", "Great choice!")
|
|
370
|
+
# ↓ IMMEDIATE PROCESSING:
|
|
371
|
+
# 1. Text combined: "I love Python Great choice!"
|
|
372
|
+
# 2. EmbeddingManager.embed() called (36ms)
|
|
373
|
+
# 3. 768D vector generated with EmbeddingGemma
|
|
374
|
+
# 4. Saved to markdown file: ./memory_files/verbatim/alice/...
|
|
375
|
+
# 5. Stored in LanceDB: vector + text + metadata
|
|
376
|
+
# 6. Interaction immediately searchable via semantic similarity
|
|
377
|
+
```
|
|
378
|
+
|
|
262
379
|
## 🔗 AbstractLLM Ecosystem Integration
|
|
263
380
|
|
|
264
|
-
AbstractMemory seamlessly integrates with
|
|
381
|
+
AbstractMemory seamlessly integrates with AbstractCore, maintaining clear separation between LLM and embedding providers:
|
|
265
382
|
|
|
266
|
-
###
|
|
383
|
+
### Critical Architecture: LLM vs Embedding Separation
|
|
267
384
|
```python
|
|
268
385
|
from abstractllm import create_llm
|
|
386
|
+
from abstractllm.embeddings import EmbeddingManager
|
|
269
387
|
from abstractmemory import create_memory
|
|
270
388
|
|
|
271
|
-
#
|
|
272
|
-
|
|
389
|
+
# SEPARATE PROVIDERS for different purposes:
|
|
390
|
+
|
|
391
|
+
# 1. LLM Provider - for TEXT GENERATION (can change freely)
|
|
392
|
+
llm_provider = create_llm("anthropic", model="claude-3-5-haiku-latest")
|
|
393
|
+
|
|
394
|
+
# 2. Embedding Provider - for SEMANTIC SEARCH (must stay consistent)
|
|
395
|
+
embedding_provider = EmbeddingManager()
|
|
273
396
|
|
|
274
|
-
# Create memory with embedding
|
|
397
|
+
# Create memory with DEDICATED embedding provider
|
|
275
398
|
memory = create_memory(
|
|
276
399
|
"grounded",
|
|
277
400
|
enable_kg=True,
|
|
278
401
|
storage_backend="dual",
|
|
279
402
|
storage_path="./memory",
|
|
280
403
|
storage_uri="./memory.db",
|
|
281
|
-
embedding_provider=
|
|
404
|
+
embedding_provider=embedding_provider # DEDICATED for embeddings
|
|
282
405
|
)
|
|
283
406
|
|
|
284
|
-
# Use
|
|
407
|
+
# Use in agent reasoning with CLEAR separation
|
|
285
408
|
context = memory.get_full_context(query)
|
|
286
|
-
response =
|
|
287
|
-
memory.add_interaction(query, response.content)
|
|
409
|
+
response = llm_provider.generate(prompt, system_prompt=context) # LLM for text
|
|
410
|
+
memory.add_interaction(query, response.content) # Embeddings handled internally
|
|
288
411
|
|
|
289
|
-
# Search
|
|
412
|
+
# Search uses embedding provider for semantic similarity
|
|
290
413
|
similar_memories = memory.search_stored_interactions("related concepts")
|
|
291
414
|
```
|
|
292
415
|
|
|
416
|
+
### Key Points:
|
|
417
|
+
- **LLM Provider**: Change freely between Anthropic ↔ OpenAI ↔ Ollama
|
|
418
|
+
- **Embedding Provider**: Must remain consistent within storage space
|
|
419
|
+
- **Never** pass LLM provider as embedding provider
|
|
420
|
+
- **Always** use dedicated embedding provider for semantic search
|
|
421
|
+
|
|
293
422
|
### With AbstractAgent (Future)
|
|
294
423
|
```python
|
|
295
424
|
from abstractagent import create_agent
|
|
@@ -306,7 +435,7 @@ response = agent.execute(task, user_id="alice")
|
|
|
306
435
|
## 🏛️ Architecture Principles
|
|
307
436
|
|
|
308
437
|
1. **No Over-Engineering**: Memory complexity matches agent requirements
|
|
309
|
-
2. **Real Implementation Testing**:
|
|
438
|
+
2. **Real Implementation Testing**: NO MOCKS anywhere - all tests use real implementations
|
|
310
439
|
3. **SOTA Research Foundation**: Built on proven patterns (MemGPT, Zep, Graphiti)
|
|
311
440
|
4. **Clean Abstractions**: Simple interfaces, powerful implementations
|
|
312
441
|
5. **Performance Optimized**: Fast operations for simple agents, scalable for complex ones
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
abstractmemory/__init__.py,sha256=
|
|
1
|
+
abstractmemory/__init__.py,sha256=lqexsZeVlJVPmWzAJZY_ftJJ-jwr3YTjr0HikYa8KwA,32473
|
|
2
2
|
abstractmemory/simple.py,sha256=mZmz7JX0zkzgvWVfC8xrpqCK1Zx43lyr4MBGWD8OQqM,4969
|
|
3
3
|
abstractmemory/cognitive/__init__.py,sha256=6L9dF3Y_OdRct7o8Hn04d4UvzlRyevNh0Etxs6CwLME,40
|
|
4
4
|
abstractmemory/components/__init__.py,sha256=mTMDpRZRaVz6HPQ9C-xvNccFD0xurfVeDP-mCZ5b3SQ,46
|
|
@@ -9,14 +9,15 @@ abstractmemory/components/working.py,sha256=4U11V8ZZzKgLs4EdcsCRTKmctQ64k9xk69_0
|
|
|
9
9
|
abstractmemory/core/__init__.py,sha256=djQvbM76uRfL4ugZgu7gW-ZEKK1wcqm0nVPXgqGq_JA,44
|
|
10
10
|
abstractmemory/core/interfaces.py,sha256=4hvoeHumBCCCyEmvmt46pFSegXHnd0c4UUN0Fp0CFi0,2757
|
|
11
11
|
abstractmemory/core/temporal.py,sha256=4BR_cAJBpX7rlFnYEN4jcdhaOLkUWaCzAYI9aPX6K0M,3526
|
|
12
|
+
abstractmemory/embeddings/__init__.py,sha256=AP_MNSiM9kUiVoenp-m6mHblGOt8ildvoJAtR54YABs,13995
|
|
12
13
|
abstractmemory/graph/__init__.py,sha256=hW927TXsGYJDc3qN4r2zrXiMl7lEk1d81h1siov1GgE,41
|
|
13
14
|
abstractmemory/graph/knowledge_graph.py,sha256=PWWBhsj2Nv4BesVGS_FjBwu88cVi1iNEHjlBRhirMlw,6805
|
|
14
15
|
abstractmemory/storage/__init__.py,sha256=GLvuzeukQ2o84AkN0yH6rB4vhLgMXsd-6up9Mw6icoE,340
|
|
15
|
-
abstractmemory/storage/dual_manager.py,sha256=
|
|
16
|
-
abstractmemory/storage/lancedb_storage.py,sha256=
|
|
16
|
+
abstractmemory/storage/dual_manager.py,sha256=HXXYjd2NRg8QTAdrDEaLk34kGcz1WjcpPzfr2qFFKeM,11355
|
|
17
|
+
abstractmemory/storage/lancedb_storage.py,sha256=QGIlJs5_0xQpZ05nfkkBL1zbJpP4QAJ1SgkfMkCXmlQ,20961
|
|
17
18
|
abstractmemory/storage/markdown_storage.py,sha256=h5EVxPrEnYw-X3AhB_8DyfiEc944MywgEDv2e6LcNZA,16054
|
|
18
|
-
abstractmemory-0.
|
|
19
|
-
abstractmemory-0.
|
|
20
|
-
abstractmemory-0.
|
|
21
|
-
abstractmemory-0.
|
|
22
|
-
abstractmemory-0.
|
|
19
|
+
abstractmemory-0.2.2.dist-info/licenses/LICENSE,sha256=NKLs197QnnfbXxdwIttTjZb1IHcj5IUTD-qXnr_V8Fw,1082
|
|
20
|
+
abstractmemory-0.2.2.dist-info/METADATA,sha256=ARGL-QSZNiui0lBmUUhx1_0sIUzUhf5HHP2SuZgz-Mg,17391
|
|
21
|
+
abstractmemory-0.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
22
|
+
abstractmemory-0.2.2.dist-info/top_level.txt,sha256=ALpTfrLlzRBy55aam5M8YuOi-4i_shfsq1DcgTR6_NQ,15
|
|
23
|
+
abstractmemory-0.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|