@hazeljs/ml 0.2.3 → 0.3.0
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/dist/evaluation/metrics.service.test.js +288 -0
- package/dist/experiments/__tests__/experiment.decorator.test.d.ts +2 -0
- package/dist/experiments/__tests__/experiment.decorator.test.d.ts.map +1 -0
- package/dist/experiments/__tests__/experiment.decorator.test.js +121 -0
- package/dist/experiments/__tests__/experiment.service.test.d.ts +2 -0
- package/dist/experiments/__tests__/experiment.service.test.d.ts.map +1 -0
- package/dist/experiments/__tests__/experiment.service.test.js +460 -0
- package/dist/experiments/experiment.decorator.d.ts +44 -0
- package/dist/experiments/experiment.decorator.d.ts.map +1 -0
- package/dist/experiments/experiment.decorator.js +51 -0
- package/dist/experiments/experiment.service.d.ts +42 -0
- package/dist/experiments/experiment.service.d.ts.map +1 -0
- package/dist/experiments/experiment.service.js +355 -0
- package/dist/experiments/experiment.types.d.ts +60 -0
- package/dist/experiments/experiment.types.d.ts.map +1 -0
- package/dist/experiments/experiment.types.js +5 -0
- package/dist/experiments/index.d.ts +9 -0
- package/dist/experiments/index.d.ts.map +1 -0
- package/dist/experiments/index.js +16 -0
- package/dist/features/__tests__/feature-view.decorator.test.d.ts +2 -0
- package/dist/features/__tests__/feature-view.decorator.test.d.ts.map +1 -0
- package/dist/features/__tests__/feature-view.decorator.test.js +168 -0
- package/dist/features/__tests__/feature.decorator.test.d.ts +2 -0
- package/dist/features/__tests__/feature.decorator.test.d.ts.map +1 -0
- package/dist/features/__tests__/feature.decorator.test.js +167 -0
- package/dist/features/feature-store.service.d.ts +59 -0
- package/dist/features/feature-store.service.d.ts.map +1 -0
- package/dist/features/feature-store.service.js +197 -0
- package/dist/features/feature-view.decorator.d.ts +52 -0
- package/dist/features/feature-view.decorator.d.ts.map +1 -0
- package/dist/features/feature-view.decorator.js +54 -0
- package/dist/features/feature.decorator.d.ts +42 -0
- package/dist/features/feature.decorator.d.ts.map +1 -0
- package/dist/features/feature.decorator.js +49 -0
- package/dist/features/feature.types.d.ts +93 -0
- package/dist/features/feature.types.d.ts.map +1 -0
- package/dist/features/feature.types.js +5 -0
- package/dist/features/index.d.ts +12 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +29 -0
- package/dist/features/offline-store.d.ts +40 -0
- package/dist/features/offline-store.d.ts.map +1 -0
- package/dist/features/offline-store.js +215 -0
- package/dist/features/online-store.d.ts +45 -0
- package/dist/features/online-store.d.ts.map +1 -0
- package/dist/features/online-store.js +139 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -1
- package/dist/monitoring/__tests__/drift.service.test.d.ts +2 -0
- package/dist/monitoring/__tests__/drift.service.test.d.ts.map +1 -0
- package/dist/monitoring/__tests__/drift.service.test.js +362 -0
- package/dist/monitoring/__tests__/monitor.service.test.d.ts +2 -0
- package/dist/monitoring/__tests__/monitor.service.test.d.ts.map +1 -0
- package/dist/monitoring/__tests__/monitor.service.test.js +360 -0
- package/dist/monitoring/drift.service.d.ts +68 -0
- package/dist/monitoring/drift.service.d.ts.map +1 -0
- package/dist/monitoring/drift.service.js +360 -0
- package/dist/monitoring/drift.types.d.ts +44 -0
- package/dist/monitoring/drift.types.d.ts.map +1 -0
- package/dist/monitoring/drift.types.js +5 -0
- package/dist/monitoring/index.d.ts +10 -0
- package/dist/monitoring/index.d.ts.map +1 -0
- package/dist/monitoring/index.js +13 -0
- package/dist/monitoring/monitor.service.d.ts +79 -0
- package/dist/monitoring/monitor.service.d.ts.map +1 -0
- package/dist/monitoring/monitor.service.js +192 -0
- package/dist/training/trainer.service.test.js +105 -0
- package/package.json +2 -2
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Monitor Service - Ongoing model monitoring and alerting
|
|
4
|
+
*/
|
|
5
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
6
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
7
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
8
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
9
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
10
|
+
};
|
|
11
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
12
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
13
|
+
};
|
|
14
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
15
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
|
+
};
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.MonitorService = void 0;
|
|
19
|
+
const core_1 = require("@hazeljs/core");
|
|
20
|
+
const core_2 = __importDefault(require("@hazeljs/core"));
|
|
21
|
+
const drift_service_1 = require("./drift.service");
|
|
22
|
+
let MonitorService = class MonitorService {
|
|
23
|
+
constructor(driftService) {
|
|
24
|
+
this.monitors = new Map();
|
|
25
|
+
this.alertHandlers = [];
|
|
26
|
+
this.checkIntervals = new Map();
|
|
27
|
+
this.accuracyHistory = new Map();
|
|
28
|
+
this.driftService = driftService;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Register a model for monitoring
|
|
32
|
+
*/
|
|
33
|
+
registerModel(config) {
|
|
34
|
+
const key = this.getMonitorKey(config.modelName, config.modelVersion);
|
|
35
|
+
this.monitors.set(key, config);
|
|
36
|
+
// Set up periodic checks if interval specified
|
|
37
|
+
if (config.checkIntervalMinutes && config.checkIntervalMinutes > 0) {
|
|
38
|
+
const intervalMs = config.checkIntervalMinutes * 60 * 1000;
|
|
39
|
+
const interval = setInterval(() => {
|
|
40
|
+
this.checkModel(config.modelName, config.modelVersion);
|
|
41
|
+
}, intervalMs);
|
|
42
|
+
// Clean up old interval if exists
|
|
43
|
+
const oldInterval = this.checkIntervals.get(key);
|
|
44
|
+
if (oldInterval) {
|
|
45
|
+
clearInterval(oldInterval);
|
|
46
|
+
}
|
|
47
|
+
this.checkIntervals.set(key, interval);
|
|
48
|
+
}
|
|
49
|
+
core_2.default.debug(`Registered monitor for ${config.modelName}@${config.modelVersion ?? 'latest'}`);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Unregister a model from monitoring
|
|
53
|
+
*/
|
|
54
|
+
unregisterModel(modelName, modelVersion) {
|
|
55
|
+
const key = this.getMonitorKey(modelName, modelVersion);
|
|
56
|
+
this.monitors.delete(key);
|
|
57
|
+
const interval = this.checkIntervals.get(key);
|
|
58
|
+
if (interval) {
|
|
59
|
+
clearInterval(interval);
|
|
60
|
+
this.checkIntervals.delete(key);
|
|
61
|
+
}
|
|
62
|
+
core_2.default.debug(`Unregistered monitor for ${modelName}@${modelVersion ?? 'latest'}`);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Add an alert handler
|
|
66
|
+
*/
|
|
67
|
+
onAlert(handler) {
|
|
68
|
+
this.alertHandlers.push(handler);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Remove an alert handler
|
|
72
|
+
*/
|
|
73
|
+
offAlert(handler) {
|
|
74
|
+
const idx = this.alertHandlers.indexOf(handler);
|
|
75
|
+
if (idx >= 0) {
|
|
76
|
+
this.alertHandlers.splice(idx, 1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Record prediction for drift monitoring
|
|
81
|
+
*/
|
|
82
|
+
recordPrediction(modelName, features, prediction) {
|
|
83
|
+
// This would store predictions for batch drift detection
|
|
84
|
+
// In a real implementation, this would write to a time-series DB
|
|
85
|
+
core_2.default.debug(`Recorded prediction for ${modelName}`, { features, prediction });
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Record accuracy metric for accuracy monitoring
|
|
89
|
+
*/
|
|
90
|
+
recordAccuracy(modelName, accuracy, modelVersion) {
|
|
91
|
+
const key = this.getMonitorKey(modelName, modelVersion);
|
|
92
|
+
const history = this.accuracyHistory.get(key) ?? [];
|
|
93
|
+
history.push({ timestamp: new Date(), accuracy });
|
|
94
|
+
this.accuracyHistory.set(key, history);
|
|
95
|
+
// Check if accuracy dropped below threshold
|
|
96
|
+
const config = this.monitors.get(key);
|
|
97
|
+
if (config?.accuracyMonitor) {
|
|
98
|
+
const { threshold, windowSize } = config.accuracyMonitor;
|
|
99
|
+
const recent = history.slice(-windowSize);
|
|
100
|
+
const avgAccuracy = recent.reduce((sum, h) => sum + h.accuracy, 0) / recent.length;
|
|
101
|
+
if (avgAccuracy < threshold) {
|
|
102
|
+
this.emitAlert({
|
|
103
|
+
timestamp: new Date(),
|
|
104
|
+
modelName,
|
|
105
|
+
modelVersion,
|
|
106
|
+
alertType: 'accuracy',
|
|
107
|
+
severity: 'critical',
|
|
108
|
+
message: `Average accuracy ${avgAccuracy.toFixed(4)} below threshold ${threshold} over last ${windowSize} checks`,
|
|
109
|
+
details: { avgAccuracy, threshold, windowSize, recentHistory: recent },
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Check a model for drift and other issues
|
|
116
|
+
*/
|
|
117
|
+
async checkModel(modelName, modelVersion) {
|
|
118
|
+
const key = this.getMonitorKey(modelName, modelVersion);
|
|
119
|
+
const config = this.monitors.get(key);
|
|
120
|
+
if (!config) {
|
|
121
|
+
throw new Error(`No monitor registered for ${modelName}@${modelVersion ?? 'latest'}`);
|
|
122
|
+
}
|
|
123
|
+
const results = [];
|
|
124
|
+
// Note: In a real implementation, this would fetch recent feature data
|
|
125
|
+
// from the feature store or prediction logs
|
|
126
|
+
// For now, this is a placeholder for the check structure
|
|
127
|
+
if (config.featureDrift) {
|
|
128
|
+
// This would be populated from actual feature data
|
|
129
|
+
const dummyFeatures = {};
|
|
130
|
+
try {
|
|
131
|
+
const report = this.driftService.detectDriftReport(dummyFeatures, config.featureDrift);
|
|
132
|
+
results.push(...report.results);
|
|
133
|
+
for (const result of report.results) {
|
|
134
|
+
if (result.driftDetected) {
|
|
135
|
+
this.emitAlert({
|
|
136
|
+
timestamp: new Date(),
|
|
137
|
+
modelName,
|
|
138
|
+
modelVersion,
|
|
139
|
+
alertType: 'drift',
|
|
140
|
+
severity: 'warning',
|
|
141
|
+
message: result.message,
|
|
142
|
+
details: { feature: result.feature, score: result.score, method: result.method },
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
core_2.default.warn(`Failed to check drift for ${modelName}:`, error);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return results;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get monitoring status for all registered models
|
|
155
|
+
*/
|
|
156
|
+
getStatus() {
|
|
157
|
+
return Array.from(this.monitors.values()).map((config) => ({
|
|
158
|
+
modelName: config.modelName,
|
|
159
|
+
modelVersion: config.modelVersion,
|
|
160
|
+
isActive: this.checkIntervals.has(this.getMonitorKey(config.modelName, config.modelVersion)),
|
|
161
|
+
checkInterval: config.checkIntervalMinutes,
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Stop all monitoring
|
|
166
|
+
*/
|
|
167
|
+
stop() {
|
|
168
|
+
for (const [key, interval] of this.checkIntervals) {
|
|
169
|
+
clearInterval(interval);
|
|
170
|
+
this.checkIntervals.delete(key);
|
|
171
|
+
}
|
|
172
|
+
core_2.default.debug('Stopped all monitoring');
|
|
173
|
+
}
|
|
174
|
+
getMonitorKey(modelName, modelVersion) {
|
|
175
|
+
return modelVersion ? `${modelName}:${modelVersion}` : modelName;
|
|
176
|
+
}
|
|
177
|
+
async emitAlert(alert) {
|
|
178
|
+
for (const handler of this.alertHandlers) {
|
|
179
|
+
try {
|
|
180
|
+
await handler(alert);
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
core_2.default.error('Alert handler failed:', error);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
exports.MonitorService = MonitorService;
|
|
189
|
+
exports.MonitorService = MonitorService = __decorate([
|
|
190
|
+
(0, core_1.Service)(),
|
|
191
|
+
__metadata("design:paramtypes", [drift_service_1.DriftService])
|
|
192
|
+
], MonitorService);
|
|
@@ -96,4 +96,109 @@ describe('TrainerService', () => {
|
|
|
96
96
|
});
|
|
97
97
|
await expect(trainer.train('broken-train', {})).rejects.toThrow('Training method train not found on model');
|
|
98
98
|
});
|
|
99
|
+
it('should train with specific version', async () => {
|
|
100
|
+
let VersionedModel = class VersionedModel {
|
|
101
|
+
async train(data) {
|
|
102
|
+
return { version: '2.0.0', data };
|
|
103
|
+
}
|
|
104
|
+
predict() { }
|
|
105
|
+
};
|
|
106
|
+
__decorate([
|
|
107
|
+
(0, decorators_1.Train)(),
|
|
108
|
+
__metadata("design:type", Function),
|
|
109
|
+
__metadata("design:paramtypes", [Object]),
|
|
110
|
+
__metadata("design:returntype", Promise)
|
|
111
|
+
], VersionedModel.prototype, "train", null);
|
|
112
|
+
__decorate([
|
|
113
|
+
(0, decorators_1.Predict)(),
|
|
114
|
+
__metadata("design:type", Function),
|
|
115
|
+
__metadata("design:paramtypes", []),
|
|
116
|
+
__metadata("design:returntype", void 0)
|
|
117
|
+
], VersionedModel.prototype, "predict", null);
|
|
118
|
+
VersionedModel = __decorate([
|
|
119
|
+
(0, decorators_1.Model)({ name: 'versioned-model', version: '2.0.0', framework: 'custom' })
|
|
120
|
+
], VersionedModel);
|
|
121
|
+
const instance = new VersionedModel();
|
|
122
|
+
registry.register({
|
|
123
|
+
metadata: { name: 'versioned-model', version: '2.0.0', framework: 'custom' },
|
|
124
|
+
instance,
|
|
125
|
+
trainMethod: 'train',
|
|
126
|
+
predictMethod: 'predict',
|
|
127
|
+
});
|
|
128
|
+
const result = await trainer.train('versioned-model', { test: true }, '2.0.0');
|
|
129
|
+
expect(result.version).toBe('2.0.0');
|
|
130
|
+
});
|
|
131
|
+
it('should handle training errors gracefully', async () => {
|
|
132
|
+
let ErrorModel = class ErrorModel {
|
|
133
|
+
async train() {
|
|
134
|
+
throw new Error('Training failed');
|
|
135
|
+
}
|
|
136
|
+
predict() { }
|
|
137
|
+
};
|
|
138
|
+
__decorate([
|
|
139
|
+
(0, decorators_1.Train)(),
|
|
140
|
+
__metadata("design:type", Function),
|
|
141
|
+
__metadata("design:paramtypes", []),
|
|
142
|
+
__metadata("design:returntype", Promise)
|
|
143
|
+
], ErrorModel.prototype, "train", null);
|
|
144
|
+
__decorate([
|
|
145
|
+
(0, decorators_1.Predict)(),
|
|
146
|
+
__metadata("design:type", Function),
|
|
147
|
+
__metadata("design:paramtypes", []),
|
|
148
|
+
__metadata("design:returntype", void 0)
|
|
149
|
+
], ErrorModel.prototype, "predict", null);
|
|
150
|
+
ErrorModel = __decorate([
|
|
151
|
+
(0, decorators_1.Model)({ name: 'error-model', version: '1.0.0', framework: 'custom' })
|
|
152
|
+
], ErrorModel);
|
|
153
|
+
const instance = new ErrorModel();
|
|
154
|
+
registry.register({
|
|
155
|
+
metadata: { name: 'error-model', version: '1.0.0', framework: 'custom' },
|
|
156
|
+
instance,
|
|
157
|
+
trainMethod: 'train',
|
|
158
|
+
predictMethod: 'predict',
|
|
159
|
+
});
|
|
160
|
+
await expect(trainer.train('error-model', {})).rejects.toThrow('Training failed');
|
|
161
|
+
});
|
|
162
|
+
it('should discover train method from decorated class', () => {
|
|
163
|
+
let DiscoverModel = class DiscoverModel {
|
|
164
|
+
customTrainMethod() { }
|
|
165
|
+
predict() { }
|
|
166
|
+
};
|
|
167
|
+
__decorate([
|
|
168
|
+
(0, decorators_1.Train)(),
|
|
169
|
+
__metadata("design:type", Function),
|
|
170
|
+
__metadata("design:paramtypes", []),
|
|
171
|
+
__metadata("design:returntype", void 0)
|
|
172
|
+
], DiscoverModel.prototype, "customTrainMethod", null);
|
|
173
|
+
__decorate([
|
|
174
|
+
(0, decorators_1.Predict)(),
|
|
175
|
+
__metadata("design:type", Function),
|
|
176
|
+
__metadata("design:paramtypes", []),
|
|
177
|
+
__metadata("design:returntype", void 0)
|
|
178
|
+
], DiscoverModel.prototype, "predict", null);
|
|
179
|
+
DiscoverModel = __decorate([
|
|
180
|
+
(0, decorators_1.Model)({ name: 'discover-model', version: '1.0.0', framework: 'custom' })
|
|
181
|
+
], DiscoverModel);
|
|
182
|
+
const instance = new DiscoverModel();
|
|
183
|
+
const method = trainer.discoverTrainMethod(instance);
|
|
184
|
+
expect(method).toBe('customTrainMethod');
|
|
185
|
+
});
|
|
186
|
+
it('should return undefined when no train method decorated', () => {
|
|
187
|
+
let NoDecoratorModel = class NoDecoratorModel {
|
|
188
|
+
train() { }
|
|
189
|
+
predict() { }
|
|
190
|
+
};
|
|
191
|
+
__decorate([
|
|
192
|
+
(0, decorators_1.Predict)(),
|
|
193
|
+
__metadata("design:type", Function),
|
|
194
|
+
__metadata("design:paramtypes", []),
|
|
195
|
+
__metadata("design:returntype", void 0)
|
|
196
|
+
], NoDecoratorModel.prototype, "predict", null);
|
|
197
|
+
NoDecoratorModel = __decorate([
|
|
198
|
+
(0, decorators_1.Model)({ name: 'no-decorator', version: '1.0.0', framework: 'custom' })
|
|
199
|
+
], NoDecoratorModel);
|
|
200
|
+
const instance = new NoDecoratorModel();
|
|
201
|
+
const method = trainer.discoverTrainMethod(instance);
|
|
202
|
+
expect(method).toBeUndefined();
|
|
203
|
+
});
|
|
99
204
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hazeljs/ml",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Machine Learning & Model Management for HazelJS framework",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -48,5 +48,5 @@
|
|
|
48
48
|
"url": "https://github.com/hazeljs/hazel-js/issues"
|
|
49
49
|
},
|
|
50
50
|
"homepage": "https://hazeljs.ai",
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "14c91213d93aa7eb907cb71b1d7104ddc4556327"
|
|
52
52
|
}
|