HazelJS ML Package
@hazeljs/ml provides machine learning model management for HazelJS with a model registry, decorator-based training/prediction APIs, batteries-included classical algorithms, batch inference, metrics tracking, feature store, experiment tracking, and drift detection.
Quick Reference
- Purpose:
@hazeljs/mlprovides ML model lifecycle management: registration, training, prediction, batch inference, metrics, feature store, experiment tracking, and drift detection — plus pure-TypeScript classifiers, recommenders, forecasting, and clustering. - When to use: Use
@hazeljs/mlfor managing ML models (training, serving, versioning). Use@hazeljs/aifor LLM integration (OpenAI, Anthropic). Use@hazeljs/datafor ETL / quality before ML (prepareTrainingData). - Key concepts: Built-in models, model registry,
@Train/@Predict, batch inference, metrics, feature store, experiments, drift monitoring. - Dependencies:
@hazeljs/core. Optional peer:@hazeljs/data. - Common patterns: Register built-in or custom model → train → predict → evaluate → monitor drift.
- Common mistakes: Not versioning models; expecting TensorFlow.js to be bundled (bring your own); using
PipelineServiceas a full ETL replacement (it is preprocess-only — use@hazeljs/datafor source→sink).
Purpose
Building ML-powered applications requires model registration, training pipelines, inference services, and evaluation metrics. The @hazeljs/ml package simplifies this by providing:
- Built-in classical ML – TF-IDF, Naive Bayes, Logistic Regression, Isolation Forest, Cosine k-NN, Item-Item CF, Jaro-Winkler entity resolution, Holt-Winters, k-means, CART decision trees (pure TypeScript, zero ML deps)
- Built-in
@Modelwrappers – Ready forMLModule.forRoot({ models: [...] }) - Model Registry – Register/discover models by name@version; optional JSON artifact persistence
- Decorator-Based API –
@Model,@Train,@Predict,@Experiment - Feature Store – Online/offline storage, point-in-time retrieval;
materialize()can stream from a@hazeljs/dataDataSource - Experiment Tracking – MLflow-style runs; auto-log when
@Experiment+@Trainare combined - Drift Detection – PSI, KS, JSD, Chi-square, Wasserstein, concept-shift helper;
MonitorServicewith real prediction windows - Training Pipeline –
PipelineServicefor preprocess-only steps before train (not a replacement for@hazeljs/dataETL) - Inference – PredictorService + BatchService (ordered results)
- Metrics – accuracy/P/R/F1, confusion matrix, MAE/MSE/RMSE/R², ROC-AUC, Brier, nDCG, MAP
- Framework-Agnostic – Bring your own TensorFlow.js / ONNX / Transformers.js class; the package does not bundle those runtimes
Architecture
The package uses a registry-based architecture with decorator-driven model registration:
graph TD A["MLModule.forRoot()<br/>(Built-in + Custom Models)"] --> B["MLModelBootstrap<br/>(Discovers @Train, @Predict)"] B --> C["ModelRegistry<br/>(Name/Version Lookup)"] D["@Model Decorator<br/>(Metadata)"] --> E["@Train / @Predict / @Experiment"] E --> B C --> F["TrainerService<br/>(Training)"] C --> G["PredictorService<br/>(Inference)"] C --> H["BatchService<br/>(Batch Predictions)"] C --> I["MetricsService<br/>(Evaluation)"] C --> FS["FeatureStore / Drift / Monitor"] G --> J["Single / Batch Prediction"] F --> K["Preprocess + Train"] style A fill:#3b82f6,stroke:#60a5fa,stroke-width:2px,color:#fff style B fill:#8b5cf6,stroke:#a78bfa,stroke-width:2px,color:#fff style C fill:#10b981,stroke:#34d399,stroke-width:2px,color:#fff style D fill:#8b5cf6,stroke:#a78bfa,stroke-width:2px,color:#fff
Key Components
- MLModule – Registers ModelRegistry, TrainerService, PipelineService, PredictorService, BatchService, MetricsService, FeatureStoreService, ExperimentService, DriftService, MonitorService
- ModelRegistry – Name/version lookup; optional
configurePersistence(dir)+saveArtifact/loadArtifact - TrainerService – Invokes
@Train; runs namedPipelineServicewhen@Train({ pipeline })is set; auto-logs@Experimentruns - PredictorService / BatchService – Single and ordered batch inference
- PipelineService – Preprocess-only training chains (use
@hazeljs/datafor production ETL) - MetricsService – Classification, regression, ranking, and calibration metrics
- Decorators –
@Model,@Train,@Predict,@Experiment
ML Decorators
Three decorators define an ML model and how it is trained and used. The registry and services discover them via reflection—no manual wiring.
@Model (class)
Attaches registry metadata so the model can be registered and looked up by name and version.
| Property | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique model id (e.g. sentiment-classifier) |
version | string | Yes | Semver (e.g. 1.0.0) |
framework | string | Yes | tensorflow | onnx | custom |
description | string | No | Human-readable description |
tags | string[] | No | Tags for filtering (default: []) |
Use one @Model per class and add @Injectable() so the app can construct the model.
@Train (method)
Marks the single method that trains the model. TrainerService.train(modelName, data) invokes it.
| Option | Type | Default | Description |
|---|---|---|---|
pipeline | string | default | Name of a registered PipelineService pipeline to run before training |
batchSize | number | 32 | Hint for batching (optional) |
epochs | number | 10 | Hint for epochs (optional) |
Exactly one @Train() method per model; it receives training data and can return TrainingResult (e.g. accuracy, loss).
@Predict (method)
Marks the single method that runs inference. PredictorService.predict(modelName, input) invokes it.
| Option | Type | Default | Description |
|---|---|---|---|
batch | boolean | false | Hint that the method supports batch input |
endpoint | string | /predict | Hint for route naming |
Exactly one @Predict() method per model; it receives one input and returns a prediction object (e.g. { sentiment, confidence }).
Rules
- One model class = one
@Model, one@Trainmethod, one@Predictmethod. - Order: Apply
@Modelon the class, then@Trainand@Predicton the methods. Use@Injectable()from@hazeljs/core. - Discovery: When you pass model classes to
MLModule.forRoot({ models: [...] }), the bootstrap finds the decorated methods and registers the model.
Advantages
1. Declarative ML
Define models with decorators—training and prediction methods are discovered automatically.
2. Model Versioning
Register multiple versions of a model; the registry supports lookup by name and version.
3. Framework Flexibility
Use TensorFlow.js, ONNX, Transformers.js, or custom implementations—the package is backend-agnostic.
4. Batch Inference
BatchService for efficient batch predictions with configurable batch size. Results preserve input order.
5. Evaluation Built-In
MetricsService with evaluate() for accuracy/F1/precision/recall, plus regression (MAE/MSE/RMSE/R²), confusion matrix, ROC-AUC, Brier, nDCG, and MAP helpers.
Installation
npm install @hazeljs/ml @hazeljs/core
# optional: validate/profile training data
npm install @hazeljs/data
Optional Peer Dependencies
# Only if you bring your own neural backends (not required for built-in models)
npm install @tensorflow/tfjs-node
npm install onnxruntime-node
npm install @huggingface/transformers
Quick Start
1. Import MLModule with a built-in model
import { HazelApp } from '@hazeljs/core';
import { MLModule, TextNaiveBayesModel } from '@hazeljs/ml';
const app = new HazelApp({
imports: [
MLModule.forRoot({
models: [TextNaiveBayesModel],
artifactDir: './models',
experiments: { storage: 'memory' },
}),
],
});
app.listen(3000);
2. Train and predict
import { TrainerService, PredictorService } from '@hazeljs/ml';
await trainer.train('text-naive-bayes', {
samples: [
{ text: 'great product', label: 'positive' },
{ text: 'terrible quality', label: 'negative' },
],
});
const result = await predictor.predict('text-naive-bayes', { text: 'I love this' });
// { label, confidence, scores }
Built-in Models
| Model name | Class | Use case |
|---|---|---|
text-naive-bayes | TextNaiveBayesModel | Ticket/chat routing, spam, intent |
text-logistic-regression | TextLogisticRegressionModel | Binary/multi-class text |
isolation-forest | IsolationForestModel | Fraud / outlier detection |
cosine-knn | CosineKnnModel | Similar tickets / k-NN |
item-item-cf | ItemItemCFModel | Recommendations |
entity-resolver | EntityResolverModel | Duplicate customers / fuzzy match |
holt-winters | HoltWintersModel | Demand / wait-time forecast |
kmeans | KMeansModel | Segmentation / clustering |
decision-tree | DecisionTreeModel | Interpretable tabular classify |
Algorithms are also exported directly (TfidfVectorizer, NaiveBayesClassifier, jaroWinkler, HoltWinters, KMeans, DecisionTreeClassifier, …) for use without decorators.
Custom Models (decorators)
You can still define your own model class:
import { Service } from '@hazeljs/core';
import { Model, Train, Predict, ModelRegistry } from '@hazeljs/ml';
@Model({ name: 'sentiment-classifier', version: '1.0.0', framework: 'custom' })
@Service()
export class SentimentClassifier {
private labels = ['positive', 'negative', 'neutral'];
private weights: Record<string, number[]> = {};
constructor(private registry: ModelRegistry) {}
@Train()
async train(data: { text: string; label: string }[]): Promise<void> {
// Your training logic – e.g. bag-of-words, embeddings
const vocab = this.buildVocabulary(data);
this.weights = this.computeWeights(data, vocab);
}
@Predict()
async predict(input: { text: string }): Promise<{ sentiment: string; confidence: number }> {
const scores = this.score(input.text);
const idx = scores.indexOf(Math.max(...scores));
return {
sentiment: this.labels[idx],
confidence: scores[idx],
};
}
}
Predict from a Controller
import { Controller, Post, Body } from '@hazeljs/core';
import { PredictorService } from '@hazeljs/ml';
@Controller('ml')
export class MLController {
constructor(private predictor: PredictorService) {}
@Post('predict')
async predict(@Body() body: { text: string; model?: string }) {
const result = await this.predictor.predict(
body.model ?? 'text-naive-bayes',
body
);
return { result };
}
}
Training Pipeline
PipelineService is preprocess-only (normalize/filter samples before TrainerService.train). For production ETL (connectors, quality, sinks), use @hazeljs/data PipelineRunner / PipelineBuilder, then pass cleaned samples via prepareTrainingData().
When @Train({ pipeline: 'name' }) is set and that pipeline is registered, TrainerService runs it automatically before calling your train method.
import { PipelineService } from '@hazeljs/ml';
const pipeline = new PipelineService();
// Inline steps (no registration required)
const steps = [
{ name: 'normalize', transform: (d: unknown) => ({ ...(d as object), text: (d as { text: string }).text?.toLowerCase() }) },
{ name: 'filter', transform: (d: unknown) => (d as { text: string }).text?.length ? d : null },
];
const processed = await pipeline.run(data, steps);
// Or register a named pipeline for @Train({ pipeline: 'default' })
pipeline.registerPipeline('default', steps);
Preparing training data with @hazeljs/data
import { Schema, QualityService } from '@hazeljs/data';
import { prepareTrainingData, TrainerService } from '@hazeljs/ml';
const SampleSchema = Schema.object({
text: Schema.string().min(1),
label: Schema.string().oneOf(['positive', 'negative']),
});
const prepared = await prepareTrainingData(
{ samples },
{ schema: SampleSchema, qualityService: new QualityService(), failOnQuality: true }
);
await trainer.train('text-naive-bayes', prepared.data);
Batch Predictions
BatchService processes inputs in batches with configurable concurrency. Results are returned in the same order as inputs.
import { BatchService } from '@hazeljs/ml';
const batchService = new BatchService(predictorService);
const results = await batchService.predictBatch('sentiment-classifier', items, {
batchSize: 32,
concurrency: 4,
});
// results[i] corresponds to items[i]
Metrics and Evaluation
Inject MetricsService via MLModule. Use evaluate() for classification metrics, or helpers for regression/ranking:
import { Injectable } from '@hazeljs/core';
import { MetricsService } from '@hazeljs/ml';
@Injectable()
class EvaluationService {
constructor(private metricsService: MetricsService) {}
async runEvaluation() {
const testData = [
{ text: 'great product', label: 'positive' },
{ text: 'terrible', label: 'negative' },
];
const evaluation = await this.metricsService.evaluate('text-naive-bayes', testData, {
metrics: ['accuracy', 'f1', 'precision', 'recall', 'confusion'],
labelKey: 'label',
predictionKey: 'label',
});
// evaluation.metrics + evaluation.confusionMatrix
// Regression helpers
const { mae, mse, rmse, r2 } = this.metricsService.computeRegressionMetrics(actual, predicted);
// Ranking helpers
const ndcg = this.metricsService.computeNDCG([3, 2, 1]);
const map = this.metricsService.computeMAP([[1, 1, 0], [0, 1, 1]]);
}
}
Manual Model Registration
When not using MLModule.forRoot({ models: [...] }):
import { registerMLModel, ModelRegistry, TrainerService, PredictorService } from '@hazeljs/ml';
registerMLModel(
sentimentInstance,
modelRegistry,
trainerService,
predictorService
);
Feature Store
TypeScript-native feature store for managing ML features with online and offline storage:
import {
FeatureStoreService,
Feature,
FeatureView,
MemoryOnlineStore,
RedisOnlineStore,
FileOfflineStore,
PostgresOfflineStore,
} from '@hazeljs/ml';
// Define features with decorators
@FeatureView({
name: 'user-behavior',
entities: ['user'],
description: 'Features derived from user behavior',
online: true,
offline: true,
})
class UserBehaviorFeatures {
@Feature({ valueType: 'number', description: 'Total login count' })
loginCount: number;
@Feature({ valueType: 'number', description: 'Average session duration in seconds' })
avgSessionDuration: number;
@Feature({ valueType: 'string', tags: ['demographic'] })
userSegment: string;
}
// Configure feature store
const featureStore = new FeatureStoreService();
featureStore.configure({
online: {
type: 'redis',
redis: { host: 'localhost', port: 6379 },
},
offline: {
type: 'postgres',
postgres: {
host: 'localhost',
port: 5432,
database: 'features',
user: 'user',
password: 'pass',
},
},
enablePointInTime: true, // Prevents data leakage in training
});
// Get features for online inference (low-latency)
const onlineFeatures = await featureStore.getOnlineFeatures(
['user123', 'user456'],
['loginCount', 'avgSessionDuration']
);
// Get historical features for training (point-in-time correct)
const trainingFeatures = await featureStore.getOfflineFeatures(
['user123'],
['loginCount', 'avgSessionDuration'],
new Date('2024-01-01') // Features as they were on this date
);
// Push features to online store
await featureStore.pushOnlineFeatures('user123', {
loginCount: 42,
avgSessionDuration: 320,
});
// Materialize from a @hazeljs/data DataSource (optional)
featureStore.registerView('user-behavior', {
name: 'user-behavior',
entities: ['user'],
features: [
{ name: 'loginCount', valueType: 'number' },
{ name: 'avgSessionDuration', valueType: 'number' },
],
source: {
type: 'batch',
config: {
dataSource: csvOrMemorySource, // any { read(): AsyncGenerator }
entityIdField: 'userId',
},
},
});
await featureStore.materialize('user-behavior', [], { toOnline: true, toOffline: true });
Feature Store Benefits
- Point-in-Time Correctness – Prevents data leakage by retrieving features as they existed at training time
- Dual Storage – Online store (Redis/Memory) for low-latency inference, offline store (Postgres/File) for training
- Type-Safe – Decorator-driven feature definitions with TypeScript types
- Zero Python Dependencies – Pure TypeScript implementation, no Feast or Python required
Experiment Tracking
MLflow-style experiment tracking with runs, metrics, parameters, and artifacts:
import { ExperimentService, Experiment } from '@hazeljs/ml';
// Configure experiment service
const experimentService = new ExperimentService();
experimentService.configure({
storage: 'file',
file: { directory: './experiments' },
});
// Create an experiment
const experiment = experimentService.createExperiment('sentiment-classifier', {
description: 'Training sentiment classification models',
tags: ['nlp', 'classification'],
});
// Start a training run
const run = experimentService.startRun(experiment.id, {
name: 'run-v1',
params: { learningRate: 0.01, epochs: 10, batchSize: 32 },
tags: ['baseline'],
});
// Log metrics during training
experimentService.logMetric(run.id, 'accuracy', 0.95);
experimentService.logMetric(run.id, 'loss', 0.05);
experimentService.logMetrics(run.id, {
precision: 0.94,
recall: 0.96,
f1Score: 0.95,
});
// Log artifacts (models, plots, logs)
experimentService.logArtifact(
run.id,
'model',
'model',
modelBuffer,
{ framework: 'tensorflow', size: modelBuffer.length }
);
// End the run
experimentService.endRun(run.id, 'completed');
// Find best run by metric
const bestRun = experimentService.getBestRun(experiment.id, 'accuracy', 'max');
console.log('Best accuracy:', bestRun.metrics.accuracy);
// Compare runs
const comparison = experimentService.compareRuns([run1.id, run2.id, run3.id]);
// [{ runId, params, metrics, durationMs }, ...]
Experiment Tracking with @Experiment Decorator
@Experiment({
name: 'sentiment-classifier',
description: 'Training sentiment classification models',
tags: ['nlp'],
autoLogParams: true,
autoLogMetrics: true,
})
@Model({ name: 'sentiment', version: '1.0.0', framework: 'custom' })
@Injectable()
class SentimentClassifier {
@Train()
async train(data: TrainingData) {
// Training runs are automatically tracked
}
}
Drift Detection & Monitoring
Production ML monitoring with statistical drift detection:
import { DriftService, MonitorService } from '@hazeljs/ml';
// Initialize drift service
const driftService = new DriftService();
// Set reference distribution from training data
driftService.setReferenceDistribution('age', trainingAges);
driftService.setReferenceDistribution('income', trainingIncomes);
// Detect drift in production data
const ageResult = driftService.detectDrift('age', productionAges, {
method: 'ks', // Kolmogorov-Smirnov test
threshold: 0.1,
});
if (ageResult.driftDetected) {
console.warn(`Drift detected: ${ageResult.message}`);
console.log(`KS statistic: ${ageResult.score}, p-value: ${ageResult.pValue}`);
}
// Run full drift report on multiple features
const report = driftService.detectDriftReport(
{
age: productionAges,
income: productionIncomes,
creditScore: productionCreditScores,
},
{
method: 'psi', // Population Stability Index
threshold: 0.25,
}
);
console.log(`Drift detected in ${report.driftedFeatures}/${report.totalFeatures} features`);
console.log(`Overall drift: ${report.overallDrift}`);
// Detect prediction drift
const predDrift = driftService.detectPredictionDrift(
trainingPredictions,
productionPredictions
);
// Set up continuous monitoring
const monitorService = new MonitorService(driftService);
monitorService.registerModel({
modelName: 'credit-risk-model',
modelVersion: '1.0.0',
featureDrift: {
method: 'ks',
threshold: 0.1,
},
accuracyMonitor: {
threshold: 0.85,
windowSize: 100,
},
checkIntervalMinutes: 60,
});
// Set up alert handler
monitorService.onAlert(async (alert) => {
console.error(`[${alert.severity}] ${alert.alertType}: ${alert.message}`);
// Send to Slack, PagerDuty, etc.
});
// Record accuracy for monitoring
monitorService.recordAccuracy('credit-risk-model', 0.92);
Drift Detection Methods
| Method | Use Case | Range |
|---|---|---|
| PSI (Population Stability Index) | Overall distribution shift | 0–∞ (>0.25 = significant) |
| KS (Kolmogorov-Smirnov) | Continuous features | 0–1 (D statistic + p-value) |
| JSD (Jensen-Shannon Divergence) | Symmetric distribution comparison | 0–0.693 |
| Chi-square | Categorical features / binned numeric (method: 'chi2') | Chi² statistic + p-value |
| Wasserstein | Earth Mover's Distance | 0–∞ (normalized by std) |
| Concept | Joint (prediction, label) shift via detectConceptDrift | Chi² on pair counts |
All statistical tests are implemented in pure TypeScript with no Python dependencies. MonitorService.recordPrediction() stores a real window used by checkModel() (not dummy features).
Service Summary
| Service | Purpose |
|---|---|
ModelRegistry | Register/lookup models; optional artifact persistence |
TrainerService | Invoke @Train (+ pipeline + experiment auto-log) |
PredictorService | Invoke @Predict |
PipelineService | Preprocess-only training pipelines |
BatchService | Batch prediction (results in input order) |
MetricsService | Classification / regression / ranking evaluation |
FeatureStoreService | Online/offline features; materialize from DataSource |
ExperimentService | Experiments, runs, metrics, artifacts |
DriftService | PSI, KS, JSD, Chi², Wasserstein, concept drift |
MonitorService | Continuous monitoring with windows + webhooks |
Recipes
Recipe: Built-in Text Classifier
import { MLModule, TextNaiveBayesModel, TrainerService, PredictorService } from '@hazeljs/ml';
MLModule.forRoot({ models: [TextNaiveBayesModel] });
await trainer.train('text-naive-bayes', {
samples: [
{ text: 'refund please', label: 'billing' },
{ text: 'app crashed', label: 'bug' },
],
});
const out = await predictor.predict('text-naive-bayes', { text: 'charge me twice' });
Recipe: Serve ML Predictions via REST
import { Controller, Post, Body } from '@hazeljs/core';
import { PredictorService } from '@hazeljs/ml';
@Controller('ml')
export class MLController {
constructor(private readonly predictor: PredictorService) {}
@Post('predict')
async predict(@Body() body: { model?: string; text: string }) {
const result = await this.predictor.predict(body.model ?? 'text-naive-bayes', {
text: body.text,
});
return { result };
}
}
Recipe: Feature Store with Online/Offline Access
import { Service } from '@hazeljs/core';
import { FeatureStoreService } from '@hazeljs/ml';
@Service()
export class FeatureService {
constructor(private readonly features: FeatureStoreService) {}
async storeUserFeatures(userId: string, feats: Record<string, number>) {
await this.features.pushOnlineFeatures(userId, feats);
await this.features.writeOfflineFeatures(userId, feats, new Date());
}
async getOnline(userId: string) {
return this.features.getOnlineFeatures([userId], ['loginCount', 'avgSessionDuration']);
}
}
Related Resources
- Data Package – ETL, quality, and
prepareTrainingDatabridge - AI Package – LLM integration for hybrid AI/ML workflows
- Cache Package – Cache model outputs and embeddings
- Config Package – Model paths and API keys
- hazeljs-ml-starter – Full app with classifiers, REST API, and scripts
- example/src/ml – Minimal
@Model/@Train/@Predictexample