@sparkleideas/neural 3.5.2-patch.1
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.
- package/README.md +260 -0
- package/__tests__/README.md +235 -0
- package/__tests__/algorithms.test.ts +582 -0
- package/__tests__/patterns.test.ts +549 -0
- package/__tests__/sona.test.ts +445 -0
- package/docs/SONA_INTEGRATION.md +460 -0
- package/docs/SONA_QUICKSTART.md +168 -0
- package/examples/sona-usage.ts +318 -0
- package/package.json +23 -0
- package/src/algorithms/a2c.d.ts +86 -0
- package/src/algorithms/a2c.d.ts.map +1 -0
- package/src/algorithms/a2c.js +361 -0
- package/src/algorithms/a2c.js.map +1 -0
- package/src/algorithms/a2c.ts +478 -0
- package/src/algorithms/curiosity.d.ts +82 -0
- package/src/algorithms/curiosity.d.ts.map +1 -0
- package/src/algorithms/curiosity.js +392 -0
- package/src/algorithms/curiosity.js.map +1 -0
- package/src/algorithms/curiosity.ts +509 -0
- package/src/algorithms/decision-transformer.d.ts +82 -0
- package/src/algorithms/decision-transformer.d.ts.map +1 -0
- package/src/algorithms/decision-transformer.js +415 -0
- package/src/algorithms/decision-transformer.js.map +1 -0
- package/src/algorithms/decision-transformer.ts +521 -0
- package/src/algorithms/dqn.d.ts +72 -0
- package/src/algorithms/dqn.d.ts.map +1 -0
- package/src/algorithms/dqn.js +303 -0
- package/src/algorithms/dqn.js.map +1 -0
- package/src/algorithms/dqn.ts +382 -0
- package/src/algorithms/index.d.ts +32 -0
- package/src/algorithms/index.d.ts.map +1 -0
- package/src/algorithms/index.js +74 -0
- package/src/algorithms/index.js.map +1 -0
- package/src/algorithms/index.ts +122 -0
- package/src/algorithms/ppo.d.ts +72 -0
- package/src/algorithms/ppo.d.ts.map +1 -0
- package/src/algorithms/ppo.js +331 -0
- package/src/algorithms/ppo.js.map +1 -0
- package/src/algorithms/ppo.ts +429 -0
- package/src/algorithms/q-learning.d.ts +77 -0
- package/src/algorithms/q-learning.d.ts.map +1 -0
- package/src/algorithms/q-learning.js +259 -0
- package/src/algorithms/q-learning.js.map +1 -0
- package/src/algorithms/q-learning.ts +333 -0
- package/src/algorithms/sarsa.d.ts +82 -0
- package/src/algorithms/sarsa.d.ts.map +1 -0
- package/src/algorithms/sarsa.js +297 -0
- package/src/algorithms/sarsa.js.map +1 -0
- package/src/algorithms/sarsa.ts +383 -0
- package/src/algorithms/tmp.json +0 -0
- package/src/application/index.ts +11 -0
- package/src/application/services/neural-application-service.ts +217 -0
- package/src/domain/entities/pattern.ts +169 -0
- package/src/domain/index.ts +18 -0
- package/src/domain/services/learning-service.ts +256 -0
- package/src/index.d.ts +118 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.js +201 -0
- package/src/index.js.map +1 -0
- package/src/index.ts +363 -0
- package/src/modes/balanced.d.ts +60 -0
- package/src/modes/balanced.d.ts.map +1 -0
- package/src/modes/balanced.js +234 -0
- package/src/modes/balanced.js.map +1 -0
- package/src/modes/balanced.ts +299 -0
- package/src/modes/base.ts +163 -0
- package/src/modes/batch.d.ts +82 -0
- package/src/modes/batch.d.ts.map +1 -0
- package/src/modes/batch.js +316 -0
- package/src/modes/batch.js.map +1 -0
- package/src/modes/batch.ts +434 -0
- package/src/modes/edge.d.ts +85 -0
- package/src/modes/edge.d.ts.map +1 -0
- package/src/modes/edge.js +310 -0
- package/src/modes/edge.js.map +1 -0
- package/src/modes/edge.ts +409 -0
- package/src/modes/index.d.ts +55 -0
- package/src/modes/index.d.ts.map +1 -0
- package/src/modes/index.js +83 -0
- package/src/modes/index.js.map +1 -0
- package/src/modes/index.ts +16 -0
- package/src/modes/real-time.d.ts +58 -0
- package/src/modes/real-time.d.ts.map +1 -0
- package/src/modes/real-time.js +196 -0
- package/src/modes/real-time.js.map +1 -0
- package/src/modes/real-time.ts +257 -0
- package/src/modes/research.d.ts +79 -0
- package/src/modes/research.d.ts.map +1 -0
- package/src/modes/research.js +389 -0
- package/src/modes/research.js.map +1 -0
- package/src/modes/research.ts +486 -0
- package/src/modes/tmp.json +0 -0
- package/src/pattern-learner.d.ts +117 -0
- package/src/pattern-learner.d.ts.map +1 -0
- package/src/pattern-learner.js +603 -0
- package/src/pattern-learner.js.map +1 -0
- package/src/pattern-learner.ts +757 -0
- package/src/reasoning-bank.d.ts +259 -0
- package/src/reasoning-bank.d.ts.map +1 -0
- package/src/reasoning-bank.js +993 -0
- package/src/reasoning-bank.js.map +1 -0
- package/src/reasoning-bank.ts +1279 -0
- package/src/reasoningbank-adapter.ts +697 -0
- package/src/sona-integration.d.ts +168 -0
- package/src/sona-integration.d.ts.map +1 -0
- package/src/sona-integration.js +316 -0
- package/src/sona-integration.js.map +1 -0
- package/src/sona-integration.ts +432 -0
- package/src/sona-manager.d.ts +147 -0
- package/src/sona-manager.d.ts.map +1 -0
- package/src/sona-manager.js +695 -0
- package/src/sona-manager.js.map +1 -0
- package/src/sona-manager.ts +835 -0
- package/src/tmp.json +0 -0
- package/src/types.d.ts +431 -0
- package/src/types.d.ts.map +1 -0
- package/src/types.js +11 -0
- package/src/types.js.map +1 -0
- package/src/types.ts +590 -0
- package/tmp.json +0 -0
- package/tsconfig.json +9 -0
- package/vitest.config.ts +19 -0
|
@@ -0,0 +1,757 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern Learner
|
|
3
|
+
*
|
|
4
|
+
* Implements pattern extraction, matching, and evolution for
|
|
5
|
+
* continuous learning from agent experiences.
|
|
6
|
+
*
|
|
7
|
+
* Performance Targets:
|
|
8
|
+
* - Pattern matching: <1ms
|
|
9
|
+
* - Pattern extraction: <5ms
|
|
10
|
+
* - Evolution step: <2ms
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
Pattern,
|
|
15
|
+
PatternMatch,
|
|
16
|
+
PatternEvolution,
|
|
17
|
+
Trajectory,
|
|
18
|
+
DistilledMemory,
|
|
19
|
+
NeuralEvent,
|
|
20
|
+
NeuralEventListener,
|
|
21
|
+
} from './types.js';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Configuration for Pattern Learner
|
|
25
|
+
*/
|
|
26
|
+
export interface PatternLearnerConfig {
|
|
27
|
+
/** Maximum number of patterns to store */
|
|
28
|
+
maxPatterns: number;
|
|
29
|
+
|
|
30
|
+
/** Similarity threshold for matching */
|
|
31
|
+
matchThreshold: number;
|
|
32
|
+
|
|
33
|
+
/** Minimum usages before pattern is stable */
|
|
34
|
+
minUsagesForStable: number;
|
|
35
|
+
|
|
36
|
+
/** Quality threshold for pattern inclusion */
|
|
37
|
+
qualityThreshold: number;
|
|
38
|
+
|
|
39
|
+
/** Enable pattern clustering */
|
|
40
|
+
enableClustering: boolean;
|
|
41
|
+
|
|
42
|
+
/** Number of clusters (if clustering enabled) */
|
|
43
|
+
numClusters: number;
|
|
44
|
+
|
|
45
|
+
/** Evolution learning rate */
|
|
46
|
+
evolutionLearningRate: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Default Pattern Learner configuration
|
|
51
|
+
*/
|
|
52
|
+
const DEFAULT_CONFIG: PatternLearnerConfig = {
|
|
53
|
+
maxPatterns: 1000,
|
|
54
|
+
matchThreshold: 0.7,
|
|
55
|
+
minUsagesForStable: 5,
|
|
56
|
+
qualityThreshold: 0.5,
|
|
57
|
+
enableClustering: true,
|
|
58
|
+
numClusters: 50,
|
|
59
|
+
evolutionLearningRate: 0.1,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Cluster representation for fast pattern lookup
|
|
64
|
+
*/
|
|
65
|
+
interface PatternCluster {
|
|
66
|
+
clusterId: number;
|
|
67
|
+
centroid: Float32Array;
|
|
68
|
+
patternIds: Set<string>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Pattern Learner - Manages pattern extraction, matching, and evolution
|
|
73
|
+
*/
|
|
74
|
+
export class PatternLearner {
|
|
75
|
+
private config: PatternLearnerConfig;
|
|
76
|
+
private patterns: Map<string, Pattern> = new Map();
|
|
77
|
+
private clusters: PatternCluster[] = [];
|
|
78
|
+
private patternToCluster: Map<string, number> = new Map();
|
|
79
|
+
|
|
80
|
+
// Performance tracking
|
|
81
|
+
private matchCount = 0;
|
|
82
|
+
private totalMatchTime = 0;
|
|
83
|
+
private extractionCount = 0;
|
|
84
|
+
private totalExtractionTime = 0;
|
|
85
|
+
private evolutionCount = 0;
|
|
86
|
+
private totalEvolutionTime = 0;
|
|
87
|
+
|
|
88
|
+
// Event listeners
|
|
89
|
+
private eventListeners: Set<NeuralEventListener> = new Set();
|
|
90
|
+
|
|
91
|
+
constructor(config: Partial<PatternLearnerConfig> = {}) {
|
|
92
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ==========================================================================
|
|
96
|
+
// Pattern Matching
|
|
97
|
+
// ==========================================================================
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Find matching patterns for a query embedding
|
|
101
|
+
* Target: <1ms
|
|
102
|
+
*/
|
|
103
|
+
findMatches(queryEmbedding: Float32Array, k: number = 3): PatternMatch[] {
|
|
104
|
+
const startTime = performance.now();
|
|
105
|
+
|
|
106
|
+
if (this.patterns.size === 0) {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let candidates: Pattern[];
|
|
111
|
+
|
|
112
|
+
// Use clustering for faster search if enabled and clusters exist
|
|
113
|
+
if (this.config.enableClustering && this.clusters.length > 0) {
|
|
114
|
+
candidates = this.getCandidatesFromClusters(queryEmbedding);
|
|
115
|
+
} else {
|
|
116
|
+
candidates = Array.from(this.patterns.values());
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Compute similarities
|
|
120
|
+
const matches: PatternMatch[] = [];
|
|
121
|
+
|
|
122
|
+
for (const pattern of candidates) {
|
|
123
|
+
const similarity = this.cosineSimilarity(queryEmbedding, pattern.embedding);
|
|
124
|
+
|
|
125
|
+
if (similarity >= this.config.matchThreshold) {
|
|
126
|
+
matches.push({
|
|
127
|
+
pattern,
|
|
128
|
+
similarity,
|
|
129
|
+
confidence: this.computeMatchConfidence(pattern, similarity),
|
|
130
|
+
latencyMs: 0,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Sort by similarity
|
|
136
|
+
matches.sort((a, b) => b.similarity - a.similarity);
|
|
137
|
+
const result = matches.slice(0, k);
|
|
138
|
+
|
|
139
|
+
// Track performance
|
|
140
|
+
const elapsed = performance.now() - startTime;
|
|
141
|
+
this.matchCount++;
|
|
142
|
+
this.totalMatchTime += elapsed;
|
|
143
|
+
|
|
144
|
+
// Warn if over target
|
|
145
|
+
if (elapsed > 1) {
|
|
146
|
+
console.warn(`Pattern matching exceeded target: ${elapsed.toFixed(2)}ms > 1ms`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Find best single match
|
|
154
|
+
*/
|
|
155
|
+
findBestMatch(queryEmbedding: Float32Array): PatternMatch | null {
|
|
156
|
+
const matches = this.findMatches(queryEmbedding, 1);
|
|
157
|
+
return matches.length > 0 ? matches[0] : null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ==========================================================================
|
|
161
|
+
// Pattern Extraction
|
|
162
|
+
// ==========================================================================
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Extract a pattern from a trajectory
|
|
166
|
+
* Target: <5ms
|
|
167
|
+
*/
|
|
168
|
+
extractPattern(trajectory: Trajectory, memory?: DistilledMemory): Pattern | null {
|
|
169
|
+
const startTime = performance.now();
|
|
170
|
+
|
|
171
|
+
// Validate trajectory
|
|
172
|
+
if (!trajectory.isComplete || trajectory.qualityScore < this.config.qualityThreshold) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Check for duplicates
|
|
177
|
+
const embedding = this.computePatternEmbedding(trajectory);
|
|
178
|
+
const existing = this.findSimilarPattern(embedding, 0.95);
|
|
179
|
+
|
|
180
|
+
if (existing) {
|
|
181
|
+
// Update existing pattern instead
|
|
182
|
+
this.updatePatternFromTrajectory(existing, trajectory);
|
|
183
|
+
return existing;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Create new pattern
|
|
187
|
+
const pattern: Pattern = {
|
|
188
|
+
patternId: `pat_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
|
|
189
|
+
name: this.generatePatternName(trajectory),
|
|
190
|
+
domain: trajectory.domain,
|
|
191
|
+
embedding,
|
|
192
|
+
strategy: this.extractStrategy(trajectory),
|
|
193
|
+
successRate: trajectory.qualityScore,
|
|
194
|
+
usageCount: 1,
|
|
195
|
+
qualityHistory: [trajectory.qualityScore],
|
|
196
|
+
evolutionHistory: [],
|
|
197
|
+
createdAt: Date.now(),
|
|
198
|
+
updatedAt: Date.now(),
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// Store pattern
|
|
202
|
+
this.patterns.set(pattern.patternId, pattern);
|
|
203
|
+
|
|
204
|
+
// Update clusters if enabled
|
|
205
|
+
if (this.config.enableClustering) {
|
|
206
|
+
this.assignToCluster(pattern);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Prune if over capacity
|
|
210
|
+
if (this.patterns.size > this.config.maxPatterns) {
|
|
211
|
+
this.prunePatterns();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Track performance
|
|
215
|
+
const elapsed = performance.now() - startTime;
|
|
216
|
+
this.extractionCount++;
|
|
217
|
+
this.totalExtractionTime += elapsed;
|
|
218
|
+
|
|
219
|
+
return pattern;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Extract patterns from multiple trajectories in batch
|
|
224
|
+
*/
|
|
225
|
+
extractPatternsBatch(trajectories: Trajectory[]): Pattern[] {
|
|
226
|
+
const patterns: Pattern[] = [];
|
|
227
|
+
|
|
228
|
+
for (const trajectory of trajectories) {
|
|
229
|
+
const pattern = this.extractPattern(trajectory);
|
|
230
|
+
if (pattern) {
|
|
231
|
+
patterns.push(pattern);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Rebuild clusters after batch extraction
|
|
236
|
+
if (this.config.enableClustering && patterns.length > 10) {
|
|
237
|
+
this.rebuildClusters();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return patterns;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ==========================================================================
|
|
244
|
+
// Pattern Evolution
|
|
245
|
+
// ==========================================================================
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Evolve a pattern based on new experience
|
|
249
|
+
* Target: <2ms
|
|
250
|
+
*/
|
|
251
|
+
evolvePattern(patternId: string, quality: number, context?: string): void {
|
|
252
|
+
const startTime = performance.now();
|
|
253
|
+
|
|
254
|
+
const pattern = this.patterns.get(patternId);
|
|
255
|
+
if (!pattern) return;
|
|
256
|
+
|
|
257
|
+
const previousQuality = pattern.successRate;
|
|
258
|
+
const lr = this.config.evolutionLearningRate;
|
|
259
|
+
|
|
260
|
+
// Update quality history
|
|
261
|
+
pattern.qualityHistory.push(quality);
|
|
262
|
+
if (pattern.qualityHistory.length > 100) {
|
|
263
|
+
pattern.qualityHistory = pattern.qualityHistory.slice(-100);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Exponential moving average for success rate
|
|
267
|
+
pattern.successRate = pattern.successRate * (1 - lr) + quality * lr;
|
|
268
|
+
pattern.usageCount++;
|
|
269
|
+
pattern.updatedAt = Date.now();
|
|
270
|
+
|
|
271
|
+
// Record evolution
|
|
272
|
+
const evolutionType = this.determineEvolutionType(previousQuality, pattern.successRate);
|
|
273
|
+
pattern.evolutionHistory.push({
|
|
274
|
+
timestamp: Date.now(),
|
|
275
|
+
type: evolutionType,
|
|
276
|
+
previousQuality,
|
|
277
|
+
newQuality: pattern.successRate,
|
|
278
|
+
description: context || 'Updated from new experience',
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Keep evolution history bounded
|
|
282
|
+
if (pattern.evolutionHistory.length > 50) {
|
|
283
|
+
pattern.evolutionHistory = pattern.evolutionHistory.slice(-50);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Emit event
|
|
287
|
+
this.emitEvent({
|
|
288
|
+
type: 'pattern_evolved',
|
|
289
|
+
patternId,
|
|
290
|
+
evolutionType,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Track performance
|
|
294
|
+
const elapsed = performance.now() - startTime;
|
|
295
|
+
this.evolutionCount++;
|
|
296
|
+
this.totalEvolutionTime += elapsed;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Merge two similar patterns
|
|
301
|
+
*/
|
|
302
|
+
mergePatterns(patternId1: string, patternId2: string): Pattern | null {
|
|
303
|
+
const p1 = this.patterns.get(patternId1);
|
|
304
|
+
const p2 = this.patterns.get(patternId2);
|
|
305
|
+
|
|
306
|
+
if (!p1 || !p2) return null;
|
|
307
|
+
|
|
308
|
+
// Keep the higher quality pattern as base
|
|
309
|
+
const [keep, remove] = p1.successRate >= p2.successRate ? [p1, p2] : [p2, p1];
|
|
310
|
+
|
|
311
|
+
// Merge embeddings (weighted average)
|
|
312
|
+
const totalUsage = keep.usageCount + remove.usageCount;
|
|
313
|
+
const w1 = keep.usageCount / totalUsage;
|
|
314
|
+
const w2 = remove.usageCount / totalUsage;
|
|
315
|
+
|
|
316
|
+
for (let i = 0; i < keep.embedding.length; i++) {
|
|
317
|
+
keep.embedding[i] = keep.embedding[i] * w1 + remove.embedding[i] * w2;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Merge statistics
|
|
321
|
+
keep.usageCount += remove.usageCount;
|
|
322
|
+
keep.qualityHistory.push(...remove.qualityHistory);
|
|
323
|
+
keep.successRate = keep.qualityHistory.reduce((a, b) => a + b, 0) / keep.qualityHistory.length;
|
|
324
|
+
|
|
325
|
+
// Record merge
|
|
326
|
+
keep.evolutionHistory.push({
|
|
327
|
+
timestamp: Date.now(),
|
|
328
|
+
type: 'merge',
|
|
329
|
+
previousQuality: p1.successRate,
|
|
330
|
+
newQuality: keep.successRate,
|
|
331
|
+
description: `Merged with pattern ${remove.patternId}`,
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Remove the merged pattern
|
|
335
|
+
this.patterns.delete(remove.patternId);
|
|
336
|
+
this.patternToCluster.delete(remove.patternId);
|
|
337
|
+
|
|
338
|
+
return keep;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Split a pattern into more specific sub-patterns
|
|
343
|
+
*/
|
|
344
|
+
splitPattern(patternId: string, numSplits: number = 2): Pattern[] {
|
|
345
|
+
const pattern = this.patterns.get(patternId);
|
|
346
|
+
if (!pattern || numSplits < 2) return [];
|
|
347
|
+
|
|
348
|
+
const splits: Pattern[] = [];
|
|
349
|
+
|
|
350
|
+
for (let i = 0; i < numSplits; i++) {
|
|
351
|
+
// Create variation of embedding with noise
|
|
352
|
+
const newEmbedding = new Float32Array(pattern.embedding.length);
|
|
353
|
+
for (let j = 0; j < newEmbedding.length; j++) {
|
|
354
|
+
const noise = (Math.random() - 0.5) * 0.1;
|
|
355
|
+
newEmbedding[j] = pattern.embedding[j] + noise;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const newPattern: Pattern = {
|
|
359
|
+
patternId: `pat_${Date.now()}_${i}_${Math.random().toString(36).slice(2, 6)}`,
|
|
360
|
+
name: `${pattern.name}_split_${i}`,
|
|
361
|
+
domain: pattern.domain,
|
|
362
|
+
embedding: newEmbedding,
|
|
363
|
+
strategy: pattern.strategy,
|
|
364
|
+
successRate: pattern.successRate * 0.9, // Slight penalty for uncertainty
|
|
365
|
+
usageCount: 0,
|
|
366
|
+
qualityHistory: [],
|
|
367
|
+
evolutionHistory: [{
|
|
368
|
+
timestamp: Date.now(),
|
|
369
|
+
type: 'split',
|
|
370
|
+
previousQuality: pattern.successRate,
|
|
371
|
+
newQuality: pattern.successRate * 0.9,
|
|
372
|
+
description: `Split from pattern ${patternId}`,
|
|
373
|
+
}],
|
|
374
|
+
createdAt: Date.now(),
|
|
375
|
+
updatedAt: Date.now(),
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
this.patterns.set(newPattern.patternId, newPattern);
|
|
379
|
+
splits.push(newPattern);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Remove original pattern
|
|
383
|
+
this.patterns.delete(patternId);
|
|
384
|
+
this.patternToCluster.delete(patternId);
|
|
385
|
+
|
|
386
|
+
// Rebuild clusters
|
|
387
|
+
if (this.config.enableClustering) {
|
|
388
|
+
this.rebuildClusters();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return splits;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ==========================================================================
|
|
395
|
+
// Pattern Access
|
|
396
|
+
// ==========================================================================
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Get all patterns
|
|
400
|
+
*/
|
|
401
|
+
getPatterns(): Pattern[] {
|
|
402
|
+
return Array.from(this.patterns.values());
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Get pattern by ID
|
|
407
|
+
*/
|
|
408
|
+
getPattern(patternId: string): Pattern | undefined {
|
|
409
|
+
return this.patterns.get(patternId);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Get patterns by domain
|
|
414
|
+
*/
|
|
415
|
+
getPatternsByDomain(domain: string): Pattern[] {
|
|
416
|
+
return Array.from(this.patterns.values()).filter(p => p.domain === domain);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Get stable patterns (sufficient usage)
|
|
421
|
+
*/
|
|
422
|
+
getStablePatterns(): Pattern[] {
|
|
423
|
+
return Array.from(this.patterns.values())
|
|
424
|
+
.filter(p => p.usageCount >= this.config.minUsagesForStable);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// ==========================================================================
|
|
428
|
+
// Statistics
|
|
429
|
+
// ==========================================================================
|
|
430
|
+
|
|
431
|
+
getStats(): Record<string, number> {
|
|
432
|
+
const patterns = Array.from(this.patterns.values());
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
totalPatterns: this.patterns.size,
|
|
436
|
+
stablePatterns: patterns.filter(p => p.usageCount >= this.config.minUsagesForStable).length,
|
|
437
|
+
avgSuccessRate: patterns.length > 0
|
|
438
|
+
? patterns.reduce((s, p) => s + p.successRate, 0) / patterns.length
|
|
439
|
+
: 0,
|
|
440
|
+
avgUsageCount: patterns.length > 0
|
|
441
|
+
? patterns.reduce((s, p) => s + p.usageCount, 0) / patterns.length
|
|
442
|
+
: 0,
|
|
443
|
+
numClusters: this.clusters.length,
|
|
444
|
+
avgMatchTimeMs: this.matchCount > 0 ? this.totalMatchTime / this.matchCount : 0,
|
|
445
|
+
avgExtractionTimeMs: this.extractionCount > 0 ? this.totalExtractionTime / this.extractionCount : 0,
|
|
446
|
+
avgEvolutionTimeMs: this.evolutionCount > 0 ? this.totalEvolutionTime / this.evolutionCount : 0,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// ==========================================================================
|
|
451
|
+
// Event System
|
|
452
|
+
// ==========================================================================
|
|
453
|
+
|
|
454
|
+
addEventListener(listener: NeuralEventListener): void {
|
|
455
|
+
this.eventListeners.add(listener);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
removeEventListener(listener: NeuralEventListener): void {
|
|
459
|
+
this.eventListeners.delete(listener);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
private emitEvent(event: NeuralEvent): void {
|
|
463
|
+
for (const listener of this.eventListeners) {
|
|
464
|
+
try {
|
|
465
|
+
listener(event);
|
|
466
|
+
} catch (error) {
|
|
467
|
+
console.error('Error in PatternLearner event listener:', error);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// ==========================================================================
|
|
473
|
+
// Private Helper Methods
|
|
474
|
+
// ==========================================================================
|
|
475
|
+
|
|
476
|
+
private cosineSimilarity(a: Float32Array, b: Float32Array): number {
|
|
477
|
+
if (a.length !== b.length) return 0;
|
|
478
|
+
|
|
479
|
+
let dot = 0, normA = 0, normB = 0;
|
|
480
|
+
for (let i = 0; i < a.length; i++) {
|
|
481
|
+
dot += a[i] * b[i];
|
|
482
|
+
normA += a[i] * a[i];
|
|
483
|
+
normB += b[i] * b[i];
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
487
|
+
return denom > 0 ? dot / denom : 0;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
private computeMatchConfidence(pattern: Pattern, similarity: number): number {
|
|
491
|
+
// Combine similarity with pattern reliability
|
|
492
|
+
const usageWeight = Math.min(pattern.usageCount / 10, 1);
|
|
493
|
+
const qualityWeight = pattern.successRate;
|
|
494
|
+
|
|
495
|
+
return similarity * (1 - usageWeight * 0.2 - qualityWeight * 0.2) +
|
|
496
|
+
usageWeight * 0.1 +
|
|
497
|
+
qualityWeight * 0.1;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
private getCandidatesFromClusters(queryEmbedding: Float32Array): Pattern[] {
|
|
501
|
+
// Find nearest clusters
|
|
502
|
+
const clusterScores: Array<{ cluster: PatternCluster; score: number }> = [];
|
|
503
|
+
|
|
504
|
+
for (const cluster of this.clusters) {
|
|
505
|
+
const score = this.cosineSimilarity(queryEmbedding, cluster.centroid);
|
|
506
|
+
clusterScores.push({ cluster, score });
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
clusterScores.sort((a, b) => b.score - a.score);
|
|
510
|
+
|
|
511
|
+
// Get patterns from top 3 clusters
|
|
512
|
+
const candidates: Pattern[] = [];
|
|
513
|
+
for (const { cluster } of clusterScores.slice(0, 3)) {
|
|
514
|
+
for (const patternId of cluster.patternIds) {
|
|
515
|
+
const pattern = this.patterns.get(patternId);
|
|
516
|
+
if (pattern) {
|
|
517
|
+
candidates.push(pattern);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return candidates;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
private findSimilarPattern(embedding: Float32Array, threshold: number): Pattern | null {
|
|
526
|
+
for (const pattern of this.patterns.values()) {
|
|
527
|
+
const sim = this.cosineSimilarity(embedding, pattern.embedding);
|
|
528
|
+
if (sim >= threshold) {
|
|
529
|
+
return pattern;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
private updatePatternFromTrajectory(pattern: Pattern, trajectory: Trajectory): void {
|
|
536
|
+
// Update quality
|
|
537
|
+
pattern.qualityHistory.push(trajectory.qualityScore);
|
|
538
|
+
if (pattern.qualityHistory.length > 100) {
|
|
539
|
+
pattern.qualityHistory = pattern.qualityHistory.slice(-100);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// EMA for success rate
|
|
543
|
+
const lr = this.config.evolutionLearningRate;
|
|
544
|
+
pattern.successRate = pattern.successRate * (1 - lr) + trajectory.qualityScore * lr;
|
|
545
|
+
pattern.usageCount++;
|
|
546
|
+
pattern.updatedAt = Date.now();
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
private computePatternEmbedding(trajectory: Trajectory): Float32Array {
|
|
550
|
+
if (trajectory.steps.length === 0) {
|
|
551
|
+
return new Float32Array(768);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const dim = trajectory.steps[0].stateAfter.length;
|
|
555
|
+
const embedding = new Float32Array(dim);
|
|
556
|
+
|
|
557
|
+
// Weighted average (higher weight for later steps)
|
|
558
|
+
let totalWeight = 0;
|
|
559
|
+
for (let i = 0; i < trajectory.steps.length; i++) {
|
|
560
|
+
const weight = (i + 1) / trajectory.steps.length;
|
|
561
|
+
totalWeight += weight;
|
|
562
|
+
for (let j = 0; j < dim; j++) {
|
|
563
|
+
embedding[j] += trajectory.steps[i].stateAfter[j] * weight;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
for (let j = 0; j < dim; j++) {
|
|
568
|
+
embedding[j] /= totalWeight;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
return embedding;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
private generatePatternName(trajectory: Trajectory): string {
|
|
575
|
+
const domain = trajectory.domain;
|
|
576
|
+
const quality = trajectory.qualityScore > 0.7 ? 'high' : 'mid';
|
|
577
|
+
const steps = trajectory.steps.length > 5 ? 'complex' : 'simple';
|
|
578
|
+
return `${domain}_${quality}_${steps}_${Date.now() % 10000}`;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
private extractStrategy(trajectory: Trajectory): string {
|
|
582
|
+
const actions = trajectory.steps.map(s => s.action);
|
|
583
|
+
if (actions.length === 0) return 'empty';
|
|
584
|
+
if (actions.length <= 3) return actions.join(' -> ');
|
|
585
|
+
return `${actions.slice(0, 2).join(' -> ')} ... ${actions[actions.length - 1]}`;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
private assignToCluster(pattern: Pattern): void {
|
|
589
|
+
if (this.clusters.length === 0) {
|
|
590
|
+
// Create first cluster
|
|
591
|
+
this.clusters.push({
|
|
592
|
+
clusterId: 0,
|
|
593
|
+
centroid: new Float32Array(pattern.embedding),
|
|
594
|
+
patternIds: new Set([pattern.patternId]),
|
|
595
|
+
});
|
|
596
|
+
this.patternToCluster.set(pattern.patternId, 0);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Find nearest cluster
|
|
601
|
+
let bestCluster = 0;
|
|
602
|
+
let bestSim = -1;
|
|
603
|
+
|
|
604
|
+
for (let i = 0; i < this.clusters.length; i++) {
|
|
605
|
+
const sim = this.cosineSimilarity(pattern.embedding, this.clusters[i].centroid);
|
|
606
|
+
if (sim > bestSim) {
|
|
607
|
+
bestSim = sim;
|
|
608
|
+
bestCluster = i;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Create new cluster if not similar enough and under limit
|
|
613
|
+
if (bestSim < 0.7 && this.clusters.length < this.config.numClusters) {
|
|
614
|
+
const newId = this.clusters.length;
|
|
615
|
+
this.clusters.push({
|
|
616
|
+
clusterId: newId,
|
|
617
|
+
centroid: new Float32Array(pattern.embedding),
|
|
618
|
+
patternIds: new Set([pattern.patternId]),
|
|
619
|
+
});
|
|
620
|
+
this.patternToCluster.set(pattern.patternId, newId);
|
|
621
|
+
} else {
|
|
622
|
+
// Add to existing cluster and update centroid
|
|
623
|
+
const cluster = this.clusters[bestCluster];
|
|
624
|
+
cluster.patternIds.add(pattern.patternId);
|
|
625
|
+
this.patternToCluster.set(pattern.patternId, bestCluster);
|
|
626
|
+
this.updateClusterCentroid(cluster);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
private updateClusterCentroid(cluster: PatternCluster): void {
|
|
631
|
+
const dim = cluster.centroid.length;
|
|
632
|
+
const newCentroid = new Float32Array(dim);
|
|
633
|
+
let count = 0;
|
|
634
|
+
|
|
635
|
+
for (const patternId of cluster.patternIds) {
|
|
636
|
+
const pattern = this.patterns.get(patternId);
|
|
637
|
+
if (pattern) {
|
|
638
|
+
for (let i = 0; i < dim; i++) {
|
|
639
|
+
newCentroid[i] += pattern.embedding[i];
|
|
640
|
+
}
|
|
641
|
+
count++;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if (count > 0) {
|
|
646
|
+
for (let i = 0; i < dim; i++) {
|
|
647
|
+
newCentroid[i] /= count;
|
|
648
|
+
}
|
|
649
|
+
cluster.centroid = newCentroid;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
private rebuildClusters(): void {
|
|
654
|
+
if (this.patterns.size === 0) {
|
|
655
|
+
this.clusters = [];
|
|
656
|
+
this.patternToCluster.clear();
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const patterns = Array.from(this.patterns.values());
|
|
661
|
+
const k = Math.min(this.config.numClusters, Math.ceil(patterns.length / 5));
|
|
662
|
+
const dim = patterns[0].embedding.length;
|
|
663
|
+
|
|
664
|
+
// Initialize clusters with random patterns
|
|
665
|
+
this.clusters = [];
|
|
666
|
+
this.patternToCluster.clear();
|
|
667
|
+
|
|
668
|
+
const indices = new Set<number>();
|
|
669
|
+
while (indices.size < k && indices.size < patterns.length) {
|
|
670
|
+
indices.add(Math.floor(Math.random() * patterns.length));
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
let clusterId = 0;
|
|
674
|
+
for (const idx of indices) {
|
|
675
|
+
this.clusters.push({
|
|
676
|
+
clusterId: clusterId++,
|
|
677
|
+
centroid: new Float32Array(patterns[idx].embedding),
|
|
678
|
+
patternIds: new Set(),
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// K-means iterations
|
|
683
|
+
for (let iter = 0; iter < 10; iter++) {
|
|
684
|
+
// Clear assignments
|
|
685
|
+
for (const cluster of this.clusters) {
|
|
686
|
+
cluster.patternIds.clear();
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Assign patterns to nearest cluster
|
|
690
|
+
for (const pattern of patterns) {
|
|
691
|
+
let bestCluster = 0;
|
|
692
|
+
let bestSim = -1;
|
|
693
|
+
|
|
694
|
+
for (let c = 0; c < this.clusters.length; c++) {
|
|
695
|
+
const sim = this.cosineSimilarity(pattern.embedding, this.clusters[c].centroid);
|
|
696
|
+
if (sim > bestSim) {
|
|
697
|
+
bestSim = sim;
|
|
698
|
+
bestCluster = c;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
this.clusters[bestCluster].patternIds.add(pattern.patternId);
|
|
703
|
+
this.patternToCluster.set(pattern.patternId, bestCluster);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Update centroids
|
|
707
|
+
for (const cluster of this.clusters) {
|
|
708
|
+
this.updateClusterCentroid(cluster);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Remove empty clusters
|
|
713
|
+
this.clusters = this.clusters.filter(c => c.patternIds.size > 0);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
private prunePatterns(): void {
|
|
717
|
+
// Sort by score (quality * log(usage))
|
|
718
|
+
const scored = Array.from(this.patterns.entries())
|
|
719
|
+
.map(([id, pattern]) => ({
|
|
720
|
+
id,
|
|
721
|
+
pattern,
|
|
722
|
+
score: pattern.successRate * Math.log(pattern.usageCount + 1),
|
|
723
|
+
}))
|
|
724
|
+
.sort((a, b) => a.score - b.score);
|
|
725
|
+
|
|
726
|
+
// Remove lowest scoring patterns
|
|
727
|
+
const toRemove = scored.length - Math.floor(this.config.maxPatterns * 0.8);
|
|
728
|
+
for (let i = 0; i < toRemove && i < scored.length; i++) {
|
|
729
|
+
this.patterns.delete(scored[i].id);
|
|
730
|
+
this.patternToCluster.delete(scored[i].id);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// Rebuild clusters
|
|
734
|
+
if (this.config.enableClustering) {
|
|
735
|
+
this.rebuildClusters();
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
private determineEvolutionType(
|
|
740
|
+
prev: number,
|
|
741
|
+
curr: number
|
|
742
|
+
): 'improvement' | 'merge' | 'split' | 'prune' {
|
|
743
|
+
const delta = curr - prev;
|
|
744
|
+
if (delta > 0.05) return 'improvement';
|
|
745
|
+
if (delta < -0.15) return 'prune';
|
|
746
|
+
return 'improvement';
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Factory function for creating PatternLearner
|
|
752
|
+
*/
|
|
753
|
+
export function createPatternLearner(
|
|
754
|
+
config?: Partial<PatternLearnerConfig>
|
|
755
|
+
): PatternLearner {
|
|
756
|
+
return new PatternLearner(config);
|
|
757
|
+
}
|