@backendkit-labs/auto-learning 0.1.3 → 0.2.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/README.md +17 -0
- package/dist/{index-CtdA-dkB.d.cts → index-CuVtbErY.d.cts} +44 -18
- package/dist/{index-CtdA-dkB.d.ts → index-CuVtbErY.d.ts} +44 -18
- package/dist/index.cjs +193 -103
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -8
- package/dist/index.d.ts +21 -8
- package/dist/index.js +193 -103
- package/dist/index.js.map +1 -1
- package/dist/nestjs/index.cjs +193 -103
- package/dist/nestjs/index.cjs.map +1 -1
- package/dist/nestjs/index.d.cts +1 -1
- package/dist/nestjs/index.d.ts +1 -1
- package/dist/nestjs/index.js +193 -103
- package/dist/nestjs/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
Static resilience configuration is a guess. `@backendkit-labs/auto-learning` observes your actual traffic, detects anomalies, and adjusts thresholds continuously — so your circuit breaker opens at the right rate, your bulkhead concurrency matches real load, and your HTTP timeouts reflect actual p95 latency rather than a number someone typed four years ago.
|
|
12
12
|
|
|
13
|
+
> **Not machine learning.** This library uses descriptive statistics (averages, percentiles, standard deviation) and deterministic rules with exponential smoothing. There are no models, no training data, and no weights. The name reflects the *behavior* — the system learns what "normal" looks like for your traffic — not the technique.
|
|
14
|
+
|
|
13
15
|
Optional NestJS integration included — global interceptor that records patterns automatically, and adapters that push config changes directly to `CircuitBreakerRegistry` and `BulkheadRegistry`.
|
|
14
16
|
|
|
15
17
|
---
|
|
@@ -176,6 +178,21 @@ That's it. Every request to `GET /orders` is recorded automatically. The feedbac
|
|
|
176
178
|
|
|
177
179
|
## Core Concepts
|
|
178
180
|
|
|
181
|
+
### How it actually works (no ML)
|
|
182
|
+
|
|
183
|
+
Despite the name, this library does not use machine learning. The techniques are deliberate:
|
|
184
|
+
|
|
185
|
+
| Technique | Where it's used |
|
|
186
|
+
|-----------|----------------|
|
|
187
|
+
| Descriptive statistics (avg, p50/p95/p99, error rate) | Aggregating patterns per endpoint |
|
|
188
|
+
| Threshold comparison against a rolling baseline | Anomaly detection |
|
|
189
|
+
| Exponential smoothing: `current + (target − current) × factor` | Gradual timeout adjustment |
|
|
190
|
+
| Deterministic step rules (+1/−1, ±10×n) | Retry and circuit breaker tuning |
|
|
191
|
+
|
|
192
|
+
**Why not ML?** Statistical rules are transparent, deterministic, and need no training data. You can read the tuning logic, predict its output, and reason about its behavior in production. A neural network that adjusts your circuit breaker threshold is a black box with no explanation for why it opened your circuit at 3 AM.
|
|
193
|
+
|
|
194
|
+
The trade-off is that the rules are hand-crafted and may not fit every traffic pattern perfectly. The configuration knobs (`smoothingFactor`, `errorRateThreshold`, `latencyStdDevThreshold`) let you adapt the behavior to your system without touching the code.
|
|
195
|
+
|
|
179
196
|
### Pattern Recording
|
|
180
197
|
|
|
181
198
|
A **pattern** is a single observation of one HTTP request: method, path, status code, duration, and timestamp. Patterns are the raw data from which everything else is derived.
|
|
@@ -57,20 +57,40 @@ type LearningCycleEvent = {
|
|
|
57
57
|
durationMs: number;
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
type
|
|
61
|
-
|
|
62
|
-
readonly tag: LearningErrorTag;
|
|
60
|
+
type StorageError = {
|
|
61
|
+
readonly tag: 'STORAGE_ERROR';
|
|
63
62
|
readonly message: string;
|
|
64
63
|
readonly cause?: unknown;
|
|
65
|
-
readonly required?: number;
|
|
66
|
-
readonly actual?: number;
|
|
67
|
-
readonly key?: string;
|
|
68
|
-
readonly value?: unknown;
|
|
69
64
|
};
|
|
65
|
+
type InsufficientDataError = {
|
|
66
|
+
readonly tag: 'INSUFFICIENT_DATA';
|
|
67
|
+
readonly message: string;
|
|
68
|
+
readonly required: number;
|
|
69
|
+
readonly actual: number;
|
|
70
|
+
};
|
|
71
|
+
type InvalidConfigError = {
|
|
72
|
+
readonly tag: 'INVALID_CONFIG';
|
|
73
|
+
readonly message: string;
|
|
74
|
+
readonly key: string;
|
|
75
|
+
readonly value: unknown;
|
|
76
|
+
};
|
|
77
|
+
type AnomalyDetectionFailedError = {
|
|
78
|
+
readonly tag: 'ANOMALY_DETECTION_FAILED';
|
|
79
|
+
readonly message: string;
|
|
80
|
+
};
|
|
81
|
+
type FeedbackLoopAlreadyRunningError = {
|
|
82
|
+
readonly tag: 'FEEDBACK_LOOP_ALREADY_RUNNING';
|
|
83
|
+
readonly message: string;
|
|
84
|
+
};
|
|
85
|
+
type FeedbackLoopNotRunningError = {
|
|
86
|
+
readonly tag: 'FEEDBACK_LOOP_NOT_RUNNING';
|
|
87
|
+
readonly message: string;
|
|
88
|
+
};
|
|
89
|
+
type LearningError = StorageError | InsufficientDataError | InvalidConfigError | AnomalyDetectionFailedError | FeedbackLoopAlreadyRunningError | FeedbackLoopNotRunningError;
|
|
70
90
|
|
|
71
91
|
interface IPatternRegistry {
|
|
72
92
|
record(pattern: EndpointPattern): Result<void, LearningError>;
|
|
73
|
-
getAggregates(windowMinutes: number): Result<AggregatePattern[], LearningError>;
|
|
93
|
+
getAggregates(windowMinutes: number, windowEnd?: Date): Result<AggregatePattern[], LearningError>;
|
|
74
94
|
getHistory(endpoint: string, method: string, limit: number): Result<EndpointPattern[], LearningError>;
|
|
75
95
|
getStats(): Result<RegistryStats, LearningError>;
|
|
76
96
|
}
|
|
@@ -84,7 +104,7 @@ type RegistryStats = {
|
|
|
84
104
|
interface StorageAdapter {
|
|
85
105
|
savePattern(pattern: EndpointPattern): Result<void, LearningError>;
|
|
86
106
|
getPatterns(windowStart: Date, windowEnd: Date): Result<EndpointPattern[], LearningError>;
|
|
87
|
-
getAggregates(windowMinutes: number): Result<AggregatePattern[], LearningError>;
|
|
107
|
+
getAggregates(windowMinutes: number, windowEnd?: Date): Result<AggregatePattern[], LearningError>;
|
|
88
108
|
saveAnomaly(report: AnomalyReport$1): Result<void, LearningError>;
|
|
89
109
|
getRecentAnomalies(limit: number): Result<AnomalyReport$1[], LearningError>;
|
|
90
110
|
saveConfig(config: TunableConfig): Result<void, LearningError>;
|
|
@@ -105,7 +125,7 @@ interface ObservabilityAdapter {
|
|
|
105
125
|
}
|
|
106
126
|
|
|
107
127
|
interface IAnomalyDetector {
|
|
108
|
-
analyze(current: EndpointPattern, baseline: AggregatePattern): Result<AnomalyReport
|
|
128
|
+
analyze(current: EndpointPattern, baseline: AggregatePattern): Result<AnomalyReport[], LearningError>;
|
|
109
129
|
batchAnalyze(windowPatterns: EndpointPattern[], baselines: AggregatePattern[]): Result<AnomalyReport[], LearningError>;
|
|
110
130
|
}
|
|
111
131
|
type AnomalyReport = {
|
|
@@ -131,13 +151,15 @@ interface IConfigTuner {
|
|
|
131
151
|
getCurrentConfig(): TunableConfig;
|
|
132
152
|
tune(aggregates: AggregatePattern[], anomalies: AnomalyReport$1[]): Result<TunableConfig, LearningError>;
|
|
133
153
|
reset(): Result<TunableConfig, LearningError>;
|
|
134
|
-
onConfigChange(callback: (config: TunableConfig) => void): void;
|
|
154
|
+
onConfigChange(callback: (config: TunableConfig) => void): () => void;
|
|
135
155
|
}
|
|
136
156
|
type ConfigTunerConfig = {
|
|
137
157
|
minTimeoutMs: number;
|
|
138
158
|
maxTimeoutMs: number;
|
|
139
159
|
smoothingFactor: number;
|
|
140
160
|
adjustmentStepMs: number;
|
|
161
|
+
/** Minimum milliseconds between successive config applications. Default: 60_000 */
|
|
162
|
+
cooldownMs: number;
|
|
141
163
|
};
|
|
142
164
|
declare const DEFAULT_TUNER_CONFIG: ConfigTunerConfig;
|
|
143
165
|
|
|
@@ -146,13 +168,14 @@ interface IFeedbackLoop {
|
|
|
146
168
|
stop(): void;
|
|
147
169
|
isRunning(): boolean;
|
|
148
170
|
runOnce(): Promise<Result<LearningCycleEvent, LearningError>>;
|
|
149
|
-
onCycle(callback: (event: LearningCycleEvent) => void): void;
|
|
171
|
+
onCycle(callback: (event: LearningCycleEvent) => void): () => void;
|
|
150
172
|
}
|
|
151
173
|
type FeedbackLoopConfig = {
|
|
152
174
|
defaultIntervalMs: number;
|
|
153
175
|
windowSizeMinutes: number;
|
|
154
176
|
minSamplesBeforeTuning: number;
|
|
155
|
-
|
|
177
|
+
/** Hours to retain patterns and anomalies. Records older than this are pruned after each cycle. Default: 24 */
|
|
178
|
+
pruneTtlHours: number;
|
|
156
179
|
};
|
|
157
180
|
declare const DEFAULT_LOOP_CONFIG: FeedbackLoopConfig;
|
|
158
181
|
|
|
@@ -178,14 +201,17 @@ declare class AutoLearningCore {
|
|
|
178
201
|
stopFeedbackLoop(): void;
|
|
179
202
|
isFeedbackLoopRunning(): boolean;
|
|
180
203
|
runOnce(): Promise<Result<LearningCycleEvent, LearningError>>;
|
|
181
|
-
onConfigChange(callback: (config: TunableConfig) => void): void;
|
|
182
|
-
onCycle(callback: (event: LearningCycleEvent) => void): void;
|
|
204
|
+
onConfigChange(callback: (config: TunableConfig) => void): () => void;
|
|
205
|
+
onCycle(callback: (event: LearningCycleEvent) => void): () => void;
|
|
183
206
|
}
|
|
184
207
|
|
|
185
208
|
type AutoLearningModuleOptions = {
|
|
186
209
|
intervalMs?: number;
|
|
187
|
-
|
|
188
|
-
|
|
210
|
+
/**
|
|
211
|
+
* Set to false to skip auto-starting the feedback loop on bootstrap.
|
|
212
|
+
* Call core.startFeedbackLoop() manually when ready. Default: true.
|
|
213
|
+
*/
|
|
214
|
+
autoStart?: boolean;
|
|
189
215
|
observability?: {
|
|
190
216
|
logger?: LoggerService;
|
|
191
217
|
metrics?: {
|
|
@@ -215,4 +241,4 @@ declare const AUTO_LEARNING_OPTIONS: unique symbol;
|
|
|
215
241
|
declare const AUTO_LEARNING_INSTANCE: unique symbol;
|
|
216
242
|
declare const AUTO_LEARN_METADATA = "auto_learn_metadata";
|
|
217
243
|
|
|
218
|
-
export { type AggregatePattern as A, type ConfigTunerConfig as C, DEFAULT_ANOMALY_CONFIG as D, type EndpointPattern as E, type FeedbackLoopConfig as F, type IPatternRegistry as I, type LearningError as L, type ObservabilityAdapter as O, type RegistryStats as R, type StorageAdapter as S, type TunableConfig as T, type IAnomalyDetector as a, type AnomalyDetectorConfig as b, type AnomalyReport as c, type IConfigTuner as d, type AnomalyReport$1 as e, type IFeedbackLoop as f, type LearningCycleEvent as g, AUTO_LEARNING_INSTANCE as h, AUTO_LEARNING_OPTIONS as i, AUTO_LEARN_METADATA as j, type
|
|
244
|
+
export { type AggregatePattern as A, type ConfigTunerConfig as C, DEFAULT_ANOMALY_CONFIG as D, type EndpointPattern as E, type FeedbackLoopConfig as F, type IPatternRegistry as I, type LearningError as L, type ObservabilityAdapter as O, type RegistryStats as R, type StorageAdapter as S, type TunableConfig as T, type IAnomalyDetector as a, type AnomalyDetectorConfig as b, type AnomalyReport as c, type IConfigTuner as d, type AnomalyReport$1 as e, type IFeedbackLoop as f, type LearningCycleEvent as g, AUTO_LEARNING_INSTANCE as h, AUTO_LEARNING_OPTIONS as i, AUTO_LEARN_METADATA as j, type AnomalyDetectionFailedError as k, type AnomalySeverity as l, AutoLearn as m, type AutoLearnOptions as n, AutoLearningCore as o, type AutoLearningCoreOptions as p, AutoLearningModule as q, type AutoLearningModuleOptions as r, DEFAULT_LOOP_CONFIG as s, DEFAULT_TUNER_CONFIG as t, type FeedbackLoopAlreadyRunningError as u, type FeedbackLoopNotRunningError as v, type InsufficientDataError as w, type InvalidConfigError as x, type StorageError as y };
|
|
@@ -57,20 +57,40 @@ type LearningCycleEvent = {
|
|
|
57
57
|
durationMs: number;
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
type
|
|
61
|
-
|
|
62
|
-
readonly tag: LearningErrorTag;
|
|
60
|
+
type StorageError = {
|
|
61
|
+
readonly tag: 'STORAGE_ERROR';
|
|
63
62
|
readonly message: string;
|
|
64
63
|
readonly cause?: unknown;
|
|
65
|
-
readonly required?: number;
|
|
66
|
-
readonly actual?: number;
|
|
67
|
-
readonly key?: string;
|
|
68
|
-
readonly value?: unknown;
|
|
69
64
|
};
|
|
65
|
+
type InsufficientDataError = {
|
|
66
|
+
readonly tag: 'INSUFFICIENT_DATA';
|
|
67
|
+
readonly message: string;
|
|
68
|
+
readonly required: number;
|
|
69
|
+
readonly actual: number;
|
|
70
|
+
};
|
|
71
|
+
type InvalidConfigError = {
|
|
72
|
+
readonly tag: 'INVALID_CONFIG';
|
|
73
|
+
readonly message: string;
|
|
74
|
+
readonly key: string;
|
|
75
|
+
readonly value: unknown;
|
|
76
|
+
};
|
|
77
|
+
type AnomalyDetectionFailedError = {
|
|
78
|
+
readonly tag: 'ANOMALY_DETECTION_FAILED';
|
|
79
|
+
readonly message: string;
|
|
80
|
+
};
|
|
81
|
+
type FeedbackLoopAlreadyRunningError = {
|
|
82
|
+
readonly tag: 'FEEDBACK_LOOP_ALREADY_RUNNING';
|
|
83
|
+
readonly message: string;
|
|
84
|
+
};
|
|
85
|
+
type FeedbackLoopNotRunningError = {
|
|
86
|
+
readonly tag: 'FEEDBACK_LOOP_NOT_RUNNING';
|
|
87
|
+
readonly message: string;
|
|
88
|
+
};
|
|
89
|
+
type LearningError = StorageError | InsufficientDataError | InvalidConfigError | AnomalyDetectionFailedError | FeedbackLoopAlreadyRunningError | FeedbackLoopNotRunningError;
|
|
70
90
|
|
|
71
91
|
interface IPatternRegistry {
|
|
72
92
|
record(pattern: EndpointPattern): Result<void, LearningError>;
|
|
73
|
-
getAggregates(windowMinutes: number): Result<AggregatePattern[], LearningError>;
|
|
93
|
+
getAggregates(windowMinutes: number, windowEnd?: Date): Result<AggregatePattern[], LearningError>;
|
|
74
94
|
getHistory(endpoint: string, method: string, limit: number): Result<EndpointPattern[], LearningError>;
|
|
75
95
|
getStats(): Result<RegistryStats, LearningError>;
|
|
76
96
|
}
|
|
@@ -84,7 +104,7 @@ type RegistryStats = {
|
|
|
84
104
|
interface StorageAdapter {
|
|
85
105
|
savePattern(pattern: EndpointPattern): Result<void, LearningError>;
|
|
86
106
|
getPatterns(windowStart: Date, windowEnd: Date): Result<EndpointPattern[], LearningError>;
|
|
87
|
-
getAggregates(windowMinutes: number): Result<AggregatePattern[], LearningError>;
|
|
107
|
+
getAggregates(windowMinutes: number, windowEnd?: Date): Result<AggregatePattern[], LearningError>;
|
|
88
108
|
saveAnomaly(report: AnomalyReport$1): Result<void, LearningError>;
|
|
89
109
|
getRecentAnomalies(limit: number): Result<AnomalyReport$1[], LearningError>;
|
|
90
110
|
saveConfig(config: TunableConfig): Result<void, LearningError>;
|
|
@@ -105,7 +125,7 @@ interface ObservabilityAdapter {
|
|
|
105
125
|
}
|
|
106
126
|
|
|
107
127
|
interface IAnomalyDetector {
|
|
108
|
-
analyze(current: EndpointPattern, baseline: AggregatePattern): Result<AnomalyReport
|
|
128
|
+
analyze(current: EndpointPattern, baseline: AggregatePattern): Result<AnomalyReport[], LearningError>;
|
|
109
129
|
batchAnalyze(windowPatterns: EndpointPattern[], baselines: AggregatePattern[]): Result<AnomalyReport[], LearningError>;
|
|
110
130
|
}
|
|
111
131
|
type AnomalyReport = {
|
|
@@ -131,13 +151,15 @@ interface IConfigTuner {
|
|
|
131
151
|
getCurrentConfig(): TunableConfig;
|
|
132
152
|
tune(aggregates: AggregatePattern[], anomalies: AnomalyReport$1[]): Result<TunableConfig, LearningError>;
|
|
133
153
|
reset(): Result<TunableConfig, LearningError>;
|
|
134
|
-
onConfigChange(callback: (config: TunableConfig) => void): void;
|
|
154
|
+
onConfigChange(callback: (config: TunableConfig) => void): () => void;
|
|
135
155
|
}
|
|
136
156
|
type ConfigTunerConfig = {
|
|
137
157
|
minTimeoutMs: number;
|
|
138
158
|
maxTimeoutMs: number;
|
|
139
159
|
smoothingFactor: number;
|
|
140
160
|
adjustmentStepMs: number;
|
|
161
|
+
/** Minimum milliseconds between successive config applications. Default: 60_000 */
|
|
162
|
+
cooldownMs: number;
|
|
141
163
|
};
|
|
142
164
|
declare const DEFAULT_TUNER_CONFIG: ConfigTunerConfig;
|
|
143
165
|
|
|
@@ -146,13 +168,14 @@ interface IFeedbackLoop {
|
|
|
146
168
|
stop(): void;
|
|
147
169
|
isRunning(): boolean;
|
|
148
170
|
runOnce(): Promise<Result<LearningCycleEvent, LearningError>>;
|
|
149
|
-
onCycle(callback: (event: LearningCycleEvent) => void): void;
|
|
171
|
+
onCycle(callback: (event: LearningCycleEvent) => void): () => void;
|
|
150
172
|
}
|
|
151
173
|
type FeedbackLoopConfig = {
|
|
152
174
|
defaultIntervalMs: number;
|
|
153
175
|
windowSizeMinutes: number;
|
|
154
176
|
minSamplesBeforeTuning: number;
|
|
155
|
-
|
|
177
|
+
/** Hours to retain patterns and anomalies. Records older than this are pruned after each cycle. Default: 24 */
|
|
178
|
+
pruneTtlHours: number;
|
|
156
179
|
};
|
|
157
180
|
declare const DEFAULT_LOOP_CONFIG: FeedbackLoopConfig;
|
|
158
181
|
|
|
@@ -178,14 +201,17 @@ declare class AutoLearningCore {
|
|
|
178
201
|
stopFeedbackLoop(): void;
|
|
179
202
|
isFeedbackLoopRunning(): boolean;
|
|
180
203
|
runOnce(): Promise<Result<LearningCycleEvent, LearningError>>;
|
|
181
|
-
onConfigChange(callback: (config: TunableConfig) => void): void;
|
|
182
|
-
onCycle(callback: (event: LearningCycleEvent) => void): void;
|
|
204
|
+
onConfigChange(callback: (config: TunableConfig) => void): () => void;
|
|
205
|
+
onCycle(callback: (event: LearningCycleEvent) => void): () => void;
|
|
183
206
|
}
|
|
184
207
|
|
|
185
208
|
type AutoLearningModuleOptions = {
|
|
186
209
|
intervalMs?: number;
|
|
187
|
-
|
|
188
|
-
|
|
210
|
+
/**
|
|
211
|
+
* Set to false to skip auto-starting the feedback loop on bootstrap.
|
|
212
|
+
* Call core.startFeedbackLoop() manually when ready. Default: true.
|
|
213
|
+
*/
|
|
214
|
+
autoStart?: boolean;
|
|
189
215
|
observability?: {
|
|
190
216
|
logger?: LoggerService;
|
|
191
217
|
metrics?: {
|
|
@@ -215,4 +241,4 @@ declare const AUTO_LEARNING_OPTIONS: unique symbol;
|
|
|
215
241
|
declare const AUTO_LEARNING_INSTANCE: unique symbol;
|
|
216
242
|
declare const AUTO_LEARN_METADATA = "auto_learn_metadata";
|
|
217
243
|
|
|
218
|
-
export { type AggregatePattern as A, type ConfigTunerConfig as C, DEFAULT_ANOMALY_CONFIG as D, type EndpointPattern as E, type FeedbackLoopConfig as F, type IPatternRegistry as I, type LearningError as L, type ObservabilityAdapter as O, type RegistryStats as R, type StorageAdapter as S, type TunableConfig as T, type IAnomalyDetector as a, type AnomalyDetectorConfig as b, type AnomalyReport as c, type IConfigTuner as d, type AnomalyReport$1 as e, type IFeedbackLoop as f, type LearningCycleEvent as g, AUTO_LEARNING_INSTANCE as h, AUTO_LEARNING_OPTIONS as i, AUTO_LEARN_METADATA as j, type
|
|
244
|
+
export { type AggregatePattern as A, type ConfigTunerConfig as C, DEFAULT_ANOMALY_CONFIG as D, type EndpointPattern as E, type FeedbackLoopConfig as F, type IPatternRegistry as I, type LearningError as L, type ObservabilityAdapter as O, type RegistryStats as R, type StorageAdapter as S, type TunableConfig as T, type IAnomalyDetector as a, type AnomalyDetectorConfig as b, type AnomalyReport as c, type IConfigTuner as d, type AnomalyReport$1 as e, type IFeedbackLoop as f, type LearningCycleEvent as g, AUTO_LEARNING_INSTANCE as h, AUTO_LEARNING_OPTIONS as i, AUTO_LEARN_METADATA as j, type AnomalyDetectionFailedError as k, type AnomalySeverity as l, AutoLearn as m, type AutoLearnOptions as n, AutoLearningCore as o, type AutoLearningCoreOptions as p, AutoLearningModule as q, type AutoLearningModuleOptions as r, DEFAULT_LOOP_CONFIG as s, DEFAULT_TUNER_CONFIG as t, type FeedbackLoopAlreadyRunningError as u, type FeedbackLoopNotRunningError as v, type InsufficientDataError as w, type InvalidConfigError as x, type StorageError as y };
|