neural-memory 0.1.0__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.
- neural_memory/__init__.py +38 -0
- neural_memory/cli/__init__.py +15 -0
- neural_memory/cli/__main__.py +6 -0
- neural_memory/cli/config.py +176 -0
- neural_memory/cli/main.py +2702 -0
- neural_memory/cli/storage.py +169 -0
- neural_memory/cli/tui.py +471 -0
- neural_memory/core/__init__.py +52 -0
- neural_memory/core/brain.py +301 -0
- neural_memory/core/brain_mode.py +273 -0
- neural_memory/core/fiber.py +236 -0
- neural_memory/core/memory_types.py +331 -0
- neural_memory/core/neuron.py +168 -0
- neural_memory/core/project.py +257 -0
- neural_memory/core/synapse.py +215 -0
- neural_memory/engine/__init__.py +15 -0
- neural_memory/engine/activation.py +335 -0
- neural_memory/engine/encoder.py +391 -0
- neural_memory/engine/retrieval.py +440 -0
- neural_memory/extraction/__init__.py +42 -0
- neural_memory/extraction/entities.py +547 -0
- neural_memory/extraction/parser.py +337 -0
- neural_memory/extraction/router.py +396 -0
- neural_memory/extraction/temporal.py +428 -0
- neural_memory/mcp/__init__.py +9 -0
- neural_memory/mcp/__main__.py +6 -0
- neural_memory/mcp/server.py +621 -0
- neural_memory/py.typed +0 -0
- neural_memory/safety/__init__.py +31 -0
- neural_memory/safety/freshness.py +238 -0
- neural_memory/safety/sensitive.py +304 -0
- neural_memory/server/__init__.py +5 -0
- neural_memory/server/app.py +99 -0
- neural_memory/server/dependencies.py +33 -0
- neural_memory/server/models.py +138 -0
- neural_memory/server/routes/__init__.py +7 -0
- neural_memory/server/routes/brain.py +221 -0
- neural_memory/server/routes/memory.py +169 -0
- neural_memory/server/routes/sync.py +387 -0
- neural_memory/storage/__init__.py +17 -0
- neural_memory/storage/base.py +441 -0
- neural_memory/storage/factory.py +329 -0
- neural_memory/storage/memory_store.py +896 -0
- neural_memory/storage/shared_store.py +650 -0
- neural_memory/storage/sqlite_store.py +1613 -0
- neural_memory/sync/__init__.py +5 -0
- neural_memory/sync/client.py +435 -0
- neural_memory/unified_config.py +315 -0
- neural_memory/utils/__init__.py +5 -0
- neural_memory/utils/config.py +98 -0
- neural_memory-0.1.0.dist-info/METADATA +314 -0
- neural_memory-0.1.0.dist-info/RECORD +55 -0
- neural_memory-0.1.0.dist-info/WHEEL +4 -0
- neural_memory-0.1.0.dist-info/entry_points.txt +4 -0
- neural_memory-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
"""Memory encoder for converting experiences into neural structures."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
from neural_memory.core.fiber import Fiber
|
|
10
|
+
from neural_memory.core.neuron import Neuron, NeuronType
|
|
11
|
+
from neural_memory.core.synapse import Synapse, SynapseType
|
|
12
|
+
from neural_memory.extraction.entities import EntityExtractor, EntityType, extract_keywords
|
|
13
|
+
from neural_memory.extraction.temporal import TemporalExtractor
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from neural_memory.core.brain import BrainConfig
|
|
17
|
+
from neural_memory.storage.base import NeuralStorage
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class EncodingResult:
|
|
22
|
+
"""
|
|
23
|
+
Result of encoding a memory.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
fiber: The created memory fiber
|
|
27
|
+
neurons_created: List of newly created neurons
|
|
28
|
+
neurons_linked: List of existing neuron IDs that were linked
|
|
29
|
+
synapses_created: List of newly created synapses
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
fiber: Fiber
|
|
33
|
+
neurons_created: list[Neuron]
|
|
34
|
+
neurons_linked: list[str]
|
|
35
|
+
synapses_created: list[Synapse]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class MemoryEncoder:
|
|
39
|
+
"""
|
|
40
|
+
Encoder for converting experiences into neural structures.
|
|
41
|
+
|
|
42
|
+
The encoder:
|
|
43
|
+
1. Extracts neurons from content (time, entities, actions, concepts)
|
|
44
|
+
2. Finds existing similar neurons for de-duplication
|
|
45
|
+
3. Creates synapses based on relationships
|
|
46
|
+
4. Bundles everything into a Fiber
|
|
47
|
+
5. Auto-links with nearby temporal neurons
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
storage: NeuralStorage,
|
|
53
|
+
config: BrainConfig,
|
|
54
|
+
temporal_extractor: TemporalExtractor | None = None,
|
|
55
|
+
entity_extractor: EntityExtractor | None = None,
|
|
56
|
+
) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Initialize the encoder.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
storage: Storage backend
|
|
62
|
+
config: Brain configuration
|
|
63
|
+
temporal_extractor: Custom temporal extractor
|
|
64
|
+
entity_extractor: Custom entity extractor
|
|
65
|
+
"""
|
|
66
|
+
self._storage = storage
|
|
67
|
+
self._config = config
|
|
68
|
+
self._temporal = temporal_extractor or TemporalExtractor()
|
|
69
|
+
self._entity = entity_extractor or EntityExtractor()
|
|
70
|
+
|
|
71
|
+
async def encode(
|
|
72
|
+
self,
|
|
73
|
+
content: str,
|
|
74
|
+
timestamp: datetime | None = None,
|
|
75
|
+
metadata: dict[str, Any] | None = None,
|
|
76
|
+
tags: set[str] | None = None,
|
|
77
|
+
) -> EncodingResult:
|
|
78
|
+
"""
|
|
79
|
+
Encode content into neural structures.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
content: The text content to encode
|
|
83
|
+
timestamp: When this memory occurred (default: now)
|
|
84
|
+
metadata: Additional metadata to attach
|
|
85
|
+
tags: Optional tags for the fiber
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
EncodingResult with created structures
|
|
89
|
+
"""
|
|
90
|
+
if timestamp is None:
|
|
91
|
+
timestamp = datetime.utcnow()
|
|
92
|
+
|
|
93
|
+
neurons_created: list[Neuron] = []
|
|
94
|
+
neurons_linked: list[str] = []
|
|
95
|
+
synapses_created: list[Synapse] = []
|
|
96
|
+
|
|
97
|
+
# 1. Extract time neurons
|
|
98
|
+
time_neurons = await self._extract_time_neurons(content, timestamp)
|
|
99
|
+
neurons_created.extend(time_neurons)
|
|
100
|
+
|
|
101
|
+
# 2. Extract entity neurons
|
|
102
|
+
entity_neurons = await self._extract_entity_neurons(content)
|
|
103
|
+
neurons_created.extend(entity_neurons)
|
|
104
|
+
|
|
105
|
+
# 3. Extract concept/keyword neurons
|
|
106
|
+
concept_neurons = await self._extract_concept_neurons(content)
|
|
107
|
+
neurons_created.extend(concept_neurons)
|
|
108
|
+
|
|
109
|
+
# 4. Create the anchor neuron (main content)
|
|
110
|
+
anchor_neuron = Neuron.create(
|
|
111
|
+
type=NeuronType.CONCEPT,
|
|
112
|
+
content=content,
|
|
113
|
+
metadata={
|
|
114
|
+
"is_anchor": True,
|
|
115
|
+
"timestamp": timestamp.isoformat(),
|
|
116
|
+
**(metadata or {}),
|
|
117
|
+
},
|
|
118
|
+
)
|
|
119
|
+
await self._storage.add_neuron(anchor_neuron)
|
|
120
|
+
neurons_created.append(anchor_neuron)
|
|
121
|
+
|
|
122
|
+
# 5. Create synapses between neurons
|
|
123
|
+
all_neurons = neurons_created
|
|
124
|
+
|
|
125
|
+
# Connect anchor to time neurons
|
|
126
|
+
for time_neuron in time_neurons:
|
|
127
|
+
synapse = Synapse.create(
|
|
128
|
+
source_id=anchor_neuron.id,
|
|
129
|
+
target_id=time_neuron.id,
|
|
130
|
+
type=SynapseType.HAPPENED_AT,
|
|
131
|
+
weight=0.9,
|
|
132
|
+
)
|
|
133
|
+
await self._storage.add_synapse(synapse)
|
|
134
|
+
synapses_created.append(synapse)
|
|
135
|
+
|
|
136
|
+
# Connect anchor to entity neurons
|
|
137
|
+
for entity_neuron in entity_neurons:
|
|
138
|
+
synapse = Synapse.create(
|
|
139
|
+
source_id=anchor_neuron.id,
|
|
140
|
+
target_id=entity_neuron.id,
|
|
141
|
+
type=SynapseType.INVOLVES,
|
|
142
|
+
weight=0.8,
|
|
143
|
+
)
|
|
144
|
+
await self._storage.add_synapse(synapse)
|
|
145
|
+
synapses_created.append(synapse)
|
|
146
|
+
|
|
147
|
+
# Connect anchor to concept neurons
|
|
148
|
+
for concept_neuron in concept_neurons:
|
|
149
|
+
synapse = Synapse.create(
|
|
150
|
+
source_id=anchor_neuron.id,
|
|
151
|
+
target_id=concept_neuron.id,
|
|
152
|
+
type=SynapseType.RELATED_TO,
|
|
153
|
+
weight=0.6,
|
|
154
|
+
)
|
|
155
|
+
await self._storage.add_synapse(synapse)
|
|
156
|
+
synapses_created.append(synapse)
|
|
157
|
+
|
|
158
|
+
# Connect entities that co-occur
|
|
159
|
+
for i, neuron_a in enumerate(entity_neurons):
|
|
160
|
+
for neuron_b in entity_neurons[i + 1 :]:
|
|
161
|
+
synapse = Synapse.create(
|
|
162
|
+
source_id=neuron_a.id,
|
|
163
|
+
target_id=neuron_b.id,
|
|
164
|
+
type=SynapseType.CO_OCCURS,
|
|
165
|
+
weight=0.5,
|
|
166
|
+
)
|
|
167
|
+
await self._storage.add_synapse(synapse)
|
|
168
|
+
synapses_created.append(synapse)
|
|
169
|
+
|
|
170
|
+
# 6. Link to nearby temporal memories
|
|
171
|
+
linked = await self._link_temporal_neighbors(anchor_neuron, timestamp)
|
|
172
|
+
neurons_linked.extend(linked)
|
|
173
|
+
|
|
174
|
+
# 7. Create fiber
|
|
175
|
+
neuron_ids = {n.id for n in all_neurons}
|
|
176
|
+
synapse_ids = {s.id for s in synapses_created}
|
|
177
|
+
|
|
178
|
+
fiber = Fiber.create(
|
|
179
|
+
neuron_ids=neuron_ids,
|
|
180
|
+
synapse_ids=synapse_ids,
|
|
181
|
+
anchor_neuron_id=anchor_neuron.id,
|
|
182
|
+
time_start=timestamp,
|
|
183
|
+
time_end=timestamp,
|
|
184
|
+
tags=tags,
|
|
185
|
+
metadata=metadata,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Calculate coherence (simple: edges / possible edges)
|
|
189
|
+
possible_edges = len(neuron_ids) * (len(neuron_ids) - 1) / 2
|
|
190
|
+
coherence = len(synapse_ids) / max(1, possible_edges)
|
|
191
|
+
fiber = fiber.with_salience(min(1.0, coherence + 0.3))
|
|
192
|
+
|
|
193
|
+
await self._storage.add_fiber(fiber)
|
|
194
|
+
|
|
195
|
+
return EncodingResult(
|
|
196
|
+
fiber=fiber,
|
|
197
|
+
neurons_created=neurons_created,
|
|
198
|
+
neurons_linked=neurons_linked,
|
|
199
|
+
synapses_created=synapses_created,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
async def _extract_time_neurons(
|
|
203
|
+
self,
|
|
204
|
+
content: str,
|
|
205
|
+
reference_time: datetime,
|
|
206
|
+
) -> list[Neuron]:
|
|
207
|
+
"""Extract and create time neurons from content."""
|
|
208
|
+
neurons: list[Neuron] = []
|
|
209
|
+
|
|
210
|
+
# Extract time hints from content
|
|
211
|
+
time_hints = self._temporal.extract(content, reference_time)
|
|
212
|
+
|
|
213
|
+
for hint in time_hints:
|
|
214
|
+
# Check for existing similar time neuron
|
|
215
|
+
existing = await self._find_similar_time_neuron(hint.midpoint)
|
|
216
|
+
if existing:
|
|
217
|
+
# Use existing neuron
|
|
218
|
+
continue
|
|
219
|
+
|
|
220
|
+
neuron = Neuron.create(
|
|
221
|
+
type=NeuronType.TIME,
|
|
222
|
+
content=hint.original,
|
|
223
|
+
metadata={
|
|
224
|
+
"absolute_start": hint.absolute_start.isoformat(),
|
|
225
|
+
"absolute_end": hint.absolute_end.isoformat(),
|
|
226
|
+
"granularity": hint.granularity.value,
|
|
227
|
+
},
|
|
228
|
+
)
|
|
229
|
+
await self._storage.add_neuron(neuron)
|
|
230
|
+
neurons.append(neuron)
|
|
231
|
+
|
|
232
|
+
# Always create a time neuron for the reference timestamp
|
|
233
|
+
timestamp_neuron = Neuron.create(
|
|
234
|
+
type=NeuronType.TIME,
|
|
235
|
+
content=reference_time.strftime("%Y-%m-%d %H:%M"),
|
|
236
|
+
metadata={
|
|
237
|
+
"absolute_start": reference_time.isoformat(),
|
|
238
|
+
"absolute_end": reference_time.isoformat(),
|
|
239
|
+
"granularity": "minute",
|
|
240
|
+
},
|
|
241
|
+
)
|
|
242
|
+
await self._storage.add_neuron(timestamp_neuron)
|
|
243
|
+
neurons.append(timestamp_neuron)
|
|
244
|
+
|
|
245
|
+
return neurons
|
|
246
|
+
|
|
247
|
+
async def _find_similar_time_neuron(
|
|
248
|
+
self,
|
|
249
|
+
timestamp: datetime,
|
|
250
|
+
) -> Neuron | None:
|
|
251
|
+
"""Find existing time neuron close to given timestamp."""
|
|
252
|
+
from datetime import timedelta
|
|
253
|
+
|
|
254
|
+
# Look for time neurons within 1 hour
|
|
255
|
+
start = timestamp - timedelta(hours=1)
|
|
256
|
+
end = timestamp + timedelta(hours=1)
|
|
257
|
+
|
|
258
|
+
existing = await self._storage.find_neurons(
|
|
259
|
+
type=NeuronType.TIME,
|
|
260
|
+
time_range=(start, end),
|
|
261
|
+
limit=1,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
return existing[0] if existing else None
|
|
265
|
+
|
|
266
|
+
async def _extract_entity_neurons(
|
|
267
|
+
self,
|
|
268
|
+
content: str,
|
|
269
|
+
) -> list[Neuron]:
|
|
270
|
+
"""Extract and create entity neurons from content."""
|
|
271
|
+
neurons: list[Neuron] = []
|
|
272
|
+
|
|
273
|
+
entities = self._entity.extract(content)
|
|
274
|
+
|
|
275
|
+
for entity in entities:
|
|
276
|
+
# Map entity type to neuron type
|
|
277
|
+
neuron_type = self._entity_type_to_neuron_type(entity.type)
|
|
278
|
+
|
|
279
|
+
# Check for existing similar entity
|
|
280
|
+
existing = await self._find_similar_entity(entity.text)
|
|
281
|
+
if existing:
|
|
282
|
+
continue
|
|
283
|
+
|
|
284
|
+
neuron = Neuron.create(
|
|
285
|
+
type=neuron_type,
|
|
286
|
+
content=entity.text,
|
|
287
|
+
metadata={
|
|
288
|
+
"entity_type": entity.type.value,
|
|
289
|
+
"confidence": entity.confidence,
|
|
290
|
+
},
|
|
291
|
+
)
|
|
292
|
+
await self._storage.add_neuron(neuron)
|
|
293
|
+
neurons.append(neuron)
|
|
294
|
+
|
|
295
|
+
return neurons
|
|
296
|
+
|
|
297
|
+
def _entity_type_to_neuron_type(self, entity_type: EntityType) -> NeuronType:
|
|
298
|
+
"""Map entity type to neuron type."""
|
|
299
|
+
mapping = {
|
|
300
|
+
EntityType.PERSON: NeuronType.ENTITY,
|
|
301
|
+
EntityType.LOCATION: NeuronType.SPATIAL,
|
|
302
|
+
EntityType.ORGANIZATION: NeuronType.ENTITY,
|
|
303
|
+
EntityType.PRODUCT: NeuronType.ENTITY,
|
|
304
|
+
EntityType.EVENT: NeuronType.ACTION,
|
|
305
|
+
EntityType.UNKNOWN: NeuronType.CONCEPT,
|
|
306
|
+
}
|
|
307
|
+
return mapping.get(entity_type, NeuronType.CONCEPT)
|
|
308
|
+
|
|
309
|
+
async def _find_similar_entity(
|
|
310
|
+
self,
|
|
311
|
+
text: str,
|
|
312
|
+
) -> Neuron | None:
|
|
313
|
+
"""Find existing entity neuron with similar content."""
|
|
314
|
+
existing = await self._storage.find_neurons(
|
|
315
|
+
content_exact=text,
|
|
316
|
+
limit=1,
|
|
317
|
+
)
|
|
318
|
+
return existing[0] if existing else None
|
|
319
|
+
|
|
320
|
+
async def _extract_concept_neurons(
|
|
321
|
+
self,
|
|
322
|
+
content: str,
|
|
323
|
+
) -> list[Neuron]:
|
|
324
|
+
"""Extract and create concept neurons from keywords."""
|
|
325
|
+
neurons: list[Neuron] = []
|
|
326
|
+
|
|
327
|
+
keywords = extract_keywords(content)
|
|
328
|
+
|
|
329
|
+
# Only create neurons for significant keywords
|
|
330
|
+
for keyword in keywords[:10]: # Limit to top 10
|
|
331
|
+
if len(keyword) < 3:
|
|
332
|
+
continue
|
|
333
|
+
|
|
334
|
+
# Check for existing
|
|
335
|
+
existing = await self._storage.find_neurons(
|
|
336
|
+
type=NeuronType.CONCEPT,
|
|
337
|
+
content_exact=keyword,
|
|
338
|
+
limit=1,
|
|
339
|
+
)
|
|
340
|
+
if existing:
|
|
341
|
+
continue
|
|
342
|
+
|
|
343
|
+
neuron = Neuron.create(
|
|
344
|
+
type=NeuronType.CONCEPT,
|
|
345
|
+
content=keyword,
|
|
346
|
+
)
|
|
347
|
+
await self._storage.add_neuron(neuron)
|
|
348
|
+
neurons.append(neuron)
|
|
349
|
+
|
|
350
|
+
return neurons
|
|
351
|
+
|
|
352
|
+
async def _link_temporal_neighbors(
|
|
353
|
+
self,
|
|
354
|
+
anchor: Neuron,
|
|
355
|
+
timestamp: datetime,
|
|
356
|
+
) -> list[str]:
|
|
357
|
+
"""Link to temporally nearby memories."""
|
|
358
|
+
from datetime import timedelta
|
|
359
|
+
|
|
360
|
+
linked: list[str] = []
|
|
361
|
+
|
|
362
|
+
# Find fibers in nearby time window
|
|
363
|
+
start = timestamp - timedelta(hours=24)
|
|
364
|
+
end = timestamp + timedelta(hours=24)
|
|
365
|
+
|
|
366
|
+
nearby_fibers = await self._storage.find_fibers(
|
|
367
|
+
time_overlaps=(start, end),
|
|
368
|
+
limit=5,
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
for fiber in nearby_fibers:
|
|
372
|
+
if fiber.anchor_neuron_id == anchor.id:
|
|
373
|
+
continue
|
|
374
|
+
|
|
375
|
+
# Create temporal synapse
|
|
376
|
+
synapse = Synapse.create(
|
|
377
|
+
source_id=anchor.id,
|
|
378
|
+
target_id=fiber.anchor_neuron_id,
|
|
379
|
+
type=SynapseType.RELATED_TO,
|
|
380
|
+
weight=0.3,
|
|
381
|
+
metadata={"temporal_link": True},
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
try:
|
|
385
|
+
await self._storage.add_synapse(synapse)
|
|
386
|
+
linked.append(fiber.anchor_neuron_id)
|
|
387
|
+
except ValueError:
|
|
388
|
+
# Synapse might already exist
|
|
389
|
+
pass
|
|
390
|
+
|
|
391
|
+
return linked
|