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,396 @@
|
|
|
1
|
+
"""Query router for intelligent query routing.
|
|
2
|
+
|
|
3
|
+
MemoCore-style query routing that determines the optimal retrieval strategy
|
|
4
|
+
based on query analysis. Routes queries to:
|
|
5
|
+
- Semantic search (RAG/embeddings) for conceptual queries
|
|
6
|
+
- Temporal traversal for time-based queries
|
|
7
|
+
- Causal traversal for why/how queries
|
|
8
|
+
- Direct lookup for exact recall
|
|
9
|
+
- Pattern matching for habit/frequency queries
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from enum import IntEnum, StrEnum
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from neural_memory.extraction.parser import QueryIntent, Stimulus
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class QueryType(StrEnum):
|
|
22
|
+
"""Types of queries for routing purposes."""
|
|
23
|
+
|
|
24
|
+
SEMANTIC = "semantic" # Conceptual, meaning-based: "What do I know about auth?"
|
|
25
|
+
TEMPORAL = "temporal" # Time-based: "What did I do yesterday?"
|
|
26
|
+
CAUSAL = "causal" # Cause/effect: "Why did the build fail?"
|
|
27
|
+
DIRECT = "direct" # Exact recall: "What's Alice's email?"
|
|
28
|
+
PATTERN = "pattern" # Habit/frequency: "What do I usually do on Mondays?"
|
|
29
|
+
COMPARATIVE = "comparative" # Comparison: "How is X different from Y?"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class RouteConfidence(IntEnum):
|
|
33
|
+
"""Confidence level in route selection."""
|
|
34
|
+
|
|
35
|
+
LOW = 1 # Guessing, might be wrong
|
|
36
|
+
MEDIUM = 2 # Reasonable guess
|
|
37
|
+
HIGH = 3 # Strong signals
|
|
38
|
+
CERTAIN = 4 # Very clear query type
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class RouteDecision:
|
|
43
|
+
"""Decision about how to route a query.
|
|
44
|
+
|
|
45
|
+
Attributes:
|
|
46
|
+
primary: Primary query type to use
|
|
47
|
+
secondary: Optional fallback query type
|
|
48
|
+
confidence: How confident we are in this routing
|
|
49
|
+
signals: What signals led to this decision
|
|
50
|
+
suggested_depth: Suggested traversal depth (0-3)
|
|
51
|
+
use_embeddings: Whether to use vector search
|
|
52
|
+
time_weighted: Whether to weight by recency
|
|
53
|
+
metadata: Additional routing hints
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
primary: QueryType
|
|
57
|
+
secondary: QueryType | None = None
|
|
58
|
+
confidence: RouteConfidence = RouteConfidence.MEDIUM
|
|
59
|
+
signals: tuple[str, ...] = ()
|
|
60
|
+
suggested_depth: int = 1
|
|
61
|
+
use_embeddings: bool = False
|
|
62
|
+
time_weighted: bool = True
|
|
63
|
+
metadata: dict[str, Any] | None = None
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def should_fallback(self) -> bool:
|
|
67
|
+
"""Whether to try secondary route if primary fails."""
|
|
68
|
+
return self.secondary is not None and self.confidence <= RouteConfidence.MEDIUM
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class QueryRouter:
|
|
72
|
+
"""Routes queries to optimal retrieval strategies.
|
|
73
|
+
|
|
74
|
+
The router analyzes a Stimulus (parsed query) and determines:
|
|
75
|
+
1. What type of query this is
|
|
76
|
+
2. What retrieval strategy to use
|
|
77
|
+
3. How deep to search
|
|
78
|
+
4. Whether to use semantic search
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
# Keywords that strongly indicate query types
|
|
82
|
+
SEMANTIC_SIGNALS = frozenset(
|
|
83
|
+
[
|
|
84
|
+
# English
|
|
85
|
+
"about",
|
|
86
|
+
"related",
|
|
87
|
+
"concept",
|
|
88
|
+
"idea",
|
|
89
|
+
"understand",
|
|
90
|
+
"explain",
|
|
91
|
+
"knowledge",
|
|
92
|
+
"information",
|
|
93
|
+
"details",
|
|
94
|
+
"overview",
|
|
95
|
+
# Vietnamese
|
|
96
|
+
"về",
|
|
97
|
+
"liên quan",
|
|
98
|
+
"khái niệm",
|
|
99
|
+
"hiểu",
|
|
100
|
+
"giải thích",
|
|
101
|
+
"thông tin",
|
|
102
|
+
]
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
TEMPORAL_SIGNALS = frozenset(
|
|
106
|
+
[
|
|
107
|
+
# English
|
|
108
|
+
"when",
|
|
109
|
+
"yesterday",
|
|
110
|
+
"today",
|
|
111
|
+
"last week",
|
|
112
|
+
"ago",
|
|
113
|
+
"before",
|
|
114
|
+
"after",
|
|
115
|
+
"morning",
|
|
116
|
+
"afternoon",
|
|
117
|
+
"evening",
|
|
118
|
+
"night",
|
|
119
|
+
"recently",
|
|
120
|
+
"earlier",
|
|
121
|
+
# Vietnamese
|
|
122
|
+
"khi nào",
|
|
123
|
+
"hôm qua",
|
|
124
|
+
"hôm nay",
|
|
125
|
+
"tuần trước",
|
|
126
|
+
"trước",
|
|
127
|
+
"sau",
|
|
128
|
+
"sáng",
|
|
129
|
+
"chiều",
|
|
130
|
+
"tối",
|
|
131
|
+
"gần đây",
|
|
132
|
+
"lúc",
|
|
133
|
+
]
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
CAUSAL_SIGNALS = frozenset(
|
|
137
|
+
[
|
|
138
|
+
# English
|
|
139
|
+
"why",
|
|
140
|
+
"because",
|
|
141
|
+
"cause",
|
|
142
|
+
"reason",
|
|
143
|
+
"result",
|
|
144
|
+
"led to",
|
|
145
|
+
"caused",
|
|
146
|
+
"effect",
|
|
147
|
+
"consequence",
|
|
148
|
+
"how come",
|
|
149
|
+
# Vietnamese
|
|
150
|
+
"tại sao",
|
|
151
|
+
"vì sao",
|
|
152
|
+
"lý do",
|
|
153
|
+
"nguyên nhân",
|
|
154
|
+
"kết quả",
|
|
155
|
+
"dẫn đến",
|
|
156
|
+
]
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
DIRECT_SIGNALS = frozenset(
|
|
160
|
+
[
|
|
161
|
+
# English
|
|
162
|
+
"what is",
|
|
163
|
+
"what's",
|
|
164
|
+
"exact",
|
|
165
|
+
"specific",
|
|
166
|
+
"precisely",
|
|
167
|
+
"tell me the",
|
|
168
|
+
"give me",
|
|
169
|
+
"show me",
|
|
170
|
+
"find",
|
|
171
|
+
# Vietnamese
|
|
172
|
+
"là gì",
|
|
173
|
+
"chính xác",
|
|
174
|
+
"cụ thể",
|
|
175
|
+
"cho tôi",
|
|
176
|
+
"tìm",
|
|
177
|
+
]
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
PATTERN_SIGNALS = frozenset(
|
|
181
|
+
[
|
|
182
|
+
# English
|
|
183
|
+
"usually",
|
|
184
|
+
"typically",
|
|
185
|
+
"always",
|
|
186
|
+
"often",
|
|
187
|
+
"habit",
|
|
188
|
+
"routine",
|
|
189
|
+
"every",
|
|
190
|
+
"pattern",
|
|
191
|
+
"tend to",
|
|
192
|
+
"normally",
|
|
193
|
+
# Vietnamese
|
|
194
|
+
"thường",
|
|
195
|
+
"hay",
|
|
196
|
+
"luôn",
|
|
197
|
+
"thói quen",
|
|
198
|
+
"mỗi",
|
|
199
|
+
"xu hướng",
|
|
200
|
+
]
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
COMPARATIVE_SIGNALS = frozenset(
|
|
204
|
+
[
|
|
205
|
+
# English
|
|
206
|
+
"compare",
|
|
207
|
+
"versus",
|
|
208
|
+
"vs",
|
|
209
|
+
"difference",
|
|
210
|
+
"different",
|
|
211
|
+
"similar",
|
|
212
|
+
"better",
|
|
213
|
+
"worse",
|
|
214
|
+
"same",
|
|
215
|
+
"like",
|
|
216
|
+
# Vietnamese
|
|
217
|
+
"so sánh",
|
|
218
|
+
"khác",
|
|
219
|
+
"giống",
|
|
220
|
+
"hơn",
|
|
221
|
+
"như",
|
|
222
|
+
]
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
def route(self, stimulus: Stimulus) -> RouteDecision:
|
|
226
|
+
"""Determine the optimal route for a query.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
stimulus: Parsed query stimulus
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
RouteDecision with routing strategy
|
|
233
|
+
"""
|
|
234
|
+
query_lower = stimulus.raw_query.lower()
|
|
235
|
+
signals: list[str] = []
|
|
236
|
+
|
|
237
|
+
# Score each query type
|
|
238
|
+
scores: dict[QueryType, float] = {
|
|
239
|
+
QueryType.SEMANTIC: 0.0,
|
|
240
|
+
QueryType.TEMPORAL: 0.0,
|
|
241
|
+
QueryType.CAUSAL: 0.0,
|
|
242
|
+
QueryType.DIRECT: 0.0,
|
|
243
|
+
QueryType.PATTERN: 0.0,
|
|
244
|
+
QueryType.COMPARATIVE: 0.0,
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
# Check for signal keywords
|
|
248
|
+
for signal in self.TEMPORAL_SIGNALS:
|
|
249
|
+
if signal in query_lower:
|
|
250
|
+
scores[QueryType.TEMPORAL] += 2.0
|
|
251
|
+
signals.append(f"temporal:{signal}")
|
|
252
|
+
|
|
253
|
+
for signal in self.CAUSAL_SIGNALS:
|
|
254
|
+
if signal in query_lower:
|
|
255
|
+
scores[QueryType.CAUSAL] += 2.0
|
|
256
|
+
signals.append(f"causal:{signal}")
|
|
257
|
+
|
|
258
|
+
for signal in self.DIRECT_SIGNALS:
|
|
259
|
+
if signal in query_lower:
|
|
260
|
+
scores[QueryType.DIRECT] += 1.5
|
|
261
|
+
signals.append(f"direct:{signal}")
|
|
262
|
+
|
|
263
|
+
for signal in self.PATTERN_SIGNALS:
|
|
264
|
+
if signal in query_lower:
|
|
265
|
+
scores[QueryType.PATTERN] += 2.0
|
|
266
|
+
signals.append(f"pattern:{signal}")
|
|
267
|
+
|
|
268
|
+
for signal in self.COMPARATIVE_SIGNALS:
|
|
269
|
+
if signal in query_lower:
|
|
270
|
+
scores[QueryType.COMPARATIVE] += 2.0
|
|
271
|
+
signals.append(f"comparative:{signal}")
|
|
272
|
+
|
|
273
|
+
for signal in self.SEMANTIC_SIGNALS:
|
|
274
|
+
if signal in query_lower:
|
|
275
|
+
scores[QueryType.SEMANTIC] += 1.0
|
|
276
|
+
signals.append(f"semantic:{signal}")
|
|
277
|
+
|
|
278
|
+
# Use parsed intent to boost scores
|
|
279
|
+
intent_boosts = self._get_intent_boosts(stimulus.intent)
|
|
280
|
+
for query_type, boost in intent_boosts.items():
|
|
281
|
+
scores[query_type] += boost
|
|
282
|
+
|
|
283
|
+
# Time hints strongly indicate temporal
|
|
284
|
+
if stimulus.has_time_context:
|
|
285
|
+
scores[QueryType.TEMPORAL] += 3.0
|
|
286
|
+
signals.append("has_time_hints")
|
|
287
|
+
|
|
288
|
+
# Entities without time often indicate direct lookup
|
|
289
|
+
if stimulus.has_entities and not stimulus.has_time_context:
|
|
290
|
+
scores[QueryType.DIRECT] += 1.0
|
|
291
|
+
signals.append("entities_no_time")
|
|
292
|
+
|
|
293
|
+
# Find primary and secondary types
|
|
294
|
+
sorted_types = sorted(scores.items(), key=lambda x: x[1], reverse=True)
|
|
295
|
+
primary = sorted_types[0][0]
|
|
296
|
+
primary_score = sorted_types[0][1]
|
|
297
|
+
secondary = sorted_types[1][0] if sorted_types[1][1] > 0 else None
|
|
298
|
+
|
|
299
|
+
# Determine confidence
|
|
300
|
+
confidence = self._determine_confidence(primary_score, scores)
|
|
301
|
+
|
|
302
|
+
# Determine depth based on query type
|
|
303
|
+
suggested_depth = self._suggest_depth(primary, stimulus)
|
|
304
|
+
|
|
305
|
+
# Determine if embeddings should be used
|
|
306
|
+
use_embeddings = primary in (QueryType.SEMANTIC, QueryType.COMPARATIVE)
|
|
307
|
+
|
|
308
|
+
# Time weighting for temporal and pattern queries
|
|
309
|
+
time_weighted = primary in (QueryType.TEMPORAL, QueryType.PATTERN)
|
|
310
|
+
|
|
311
|
+
return RouteDecision(
|
|
312
|
+
primary=primary,
|
|
313
|
+
secondary=secondary,
|
|
314
|
+
confidence=confidence,
|
|
315
|
+
signals=tuple(signals),
|
|
316
|
+
suggested_depth=suggested_depth,
|
|
317
|
+
use_embeddings=use_embeddings,
|
|
318
|
+
time_weighted=time_weighted,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
def _get_intent_boosts(self, intent: QueryIntent) -> dict[QueryType, float]:
|
|
322
|
+
"""Get score boosts based on query intent."""
|
|
323
|
+
boosts: dict[QueryType, float] = {}
|
|
324
|
+
|
|
325
|
+
intent_mapping = {
|
|
326
|
+
QueryIntent.ASK_WHEN: {QueryType.TEMPORAL: 2.0},
|
|
327
|
+
QueryIntent.ASK_WHY: {QueryType.CAUSAL: 2.0},
|
|
328
|
+
QueryIntent.ASK_HOW: {QueryType.CAUSAL: 1.5, QueryType.SEMANTIC: 0.5},
|
|
329
|
+
QueryIntent.ASK_WHAT: {QueryType.SEMANTIC: 1.0, QueryType.DIRECT: 1.0},
|
|
330
|
+
QueryIntent.ASK_WHO: {QueryType.DIRECT: 1.5},
|
|
331
|
+
QueryIntent.ASK_WHERE: {QueryType.DIRECT: 1.5, QueryType.TEMPORAL: 0.5},
|
|
332
|
+
QueryIntent.ASK_PATTERN: {QueryType.PATTERN: 3.0},
|
|
333
|
+
QueryIntent.ASK_FEELING: {QueryType.TEMPORAL: 1.0, QueryType.SEMANTIC: 1.0},
|
|
334
|
+
QueryIntent.CONFIRM: {QueryType.DIRECT: 2.0},
|
|
335
|
+
QueryIntent.COMPARE: {QueryType.COMPARATIVE: 3.0},
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return intent_mapping.get(intent, boosts)
|
|
339
|
+
|
|
340
|
+
def _determine_confidence(
|
|
341
|
+
self, primary_score: float, all_scores: dict[QueryType, float]
|
|
342
|
+
) -> RouteConfidence:
|
|
343
|
+
"""Determine confidence in the routing decision."""
|
|
344
|
+
if primary_score >= 5.0:
|
|
345
|
+
return RouteConfidence.CERTAIN
|
|
346
|
+
elif primary_score >= 3.0:
|
|
347
|
+
return RouteConfidence.HIGH
|
|
348
|
+
elif primary_score >= 1.5:
|
|
349
|
+
return RouteConfidence.MEDIUM
|
|
350
|
+
else:
|
|
351
|
+
return RouteConfidence.LOW
|
|
352
|
+
|
|
353
|
+
def _suggest_depth(self, query_type: QueryType, stimulus: Stimulus) -> int:
|
|
354
|
+
"""Suggest traversal depth based on query type."""
|
|
355
|
+
depth_defaults = {
|
|
356
|
+
QueryType.DIRECT: 0, # Instant lookup
|
|
357
|
+
QueryType.TEMPORAL: 1, # Context level
|
|
358
|
+
QueryType.SEMANTIC: 2, # Habit level (broader search)
|
|
359
|
+
QueryType.CAUSAL: 2, # Need to traverse causes
|
|
360
|
+
QueryType.PATTERN: 2, # Cross-time patterns
|
|
361
|
+
QueryType.COMPARATIVE: 2, # Need multiple memories
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
base_depth = depth_defaults.get(query_type, 1)
|
|
365
|
+
|
|
366
|
+
# Increase depth for complex queries
|
|
367
|
+
if stimulus.anchor_count > 3:
|
|
368
|
+
base_depth = min(3, base_depth + 1)
|
|
369
|
+
|
|
370
|
+
return base_depth
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def route_query(stimulus: Stimulus) -> RouteDecision:
|
|
374
|
+
"""Convenience function to route a query.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
stimulus: Parsed query stimulus
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
RouteDecision with routing strategy
|
|
381
|
+
"""
|
|
382
|
+
router = QueryRouter()
|
|
383
|
+
return router.route(stimulus)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def get_query_type_description(query_type: QueryType) -> str:
|
|
387
|
+
"""Get human-readable description of a query type."""
|
|
388
|
+
descriptions = {
|
|
389
|
+
QueryType.SEMANTIC: "Conceptual search - finding related ideas and knowledge",
|
|
390
|
+
QueryType.TEMPORAL: "Time-based search - finding memories from specific times",
|
|
391
|
+
QueryType.CAUSAL: "Causal search - understanding why/how things happened",
|
|
392
|
+
QueryType.DIRECT: "Direct lookup - finding specific facts or details",
|
|
393
|
+
QueryType.PATTERN: "Pattern search - finding habits and recurring themes",
|
|
394
|
+
QueryType.COMPARATIVE: "Comparative search - comparing different memories",
|
|
395
|
+
}
|
|
396
|
+
return descriptions.get(query_type, "Unknown query type")
|