@elizaos/training 2.0.0-alpha.21 → 2.0.0-alpha.22

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.
Files changed (94) hide show
  1. package/.turbo/turbo-lint.log +2 -0
  2. package/.turbo/turbo-typecheck.log +1 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/adapter.js +59 -0
  5. package/dist/archetypes/ArchetypeConfigService.js +510 -0
  6. package/dist/archetypes/derive-archetype.js +196 -0
  7. package/dist/archetypes/index.js +7 -0
  8. package/dist/benchmark/ArchetypeMatchupBenchmark.js +547 -0
  9. package/dist/benchmark/BenchmarkChartGenerator.js +632 -0
  10. package/dist/benchmark/BenchmarkDataGenerator.js +825 -0
  11. package/dist/benchmark/BenchmarkDataViewer.js +197 -0
  12. package/dist/benchmark/BenchmarkHistoryService.js +135 -0
  13. package/dist/benchmark/BenchmarkRunner.js +483 -0
  14. package/dist/benchmark/BenchmarkValidator.js +158 -0
  15. package/dist/benchmark/FastEvalRunner.js +133 -0
  16. package/dist/benchmark/MetricsValidator.js +104 -0
  17. package/dist/benchmark/MetricsVisualizer.js +775 -0
  18. package/dist/benchmark/ModelBenchmarkService.js +433 -0
  19. package/dist/benchmark/ModelRegistry.js +122 -0
  20. package/dist/benchmark/RulerBenchmarkIntegration.js +168 -0
  21. package/dist/benchmark/SimulationA2AInterface.js +683 -0
  22. package/dist/benchmark/SimulationEngine.js +522 -0
  23. package/dist/benchmark/TaskRunner.js +60 -0
  24. package/dist/benchmark/__tests__/BenchmarkRunner.test.js +409 -0
  25. package/dist/benchmark/__tests__/HeadToHead.test.js +105 -0
  26. package/dist/benchmark/index.js +23 -0
  27. package/dist/benchmark/parseSimulationMetrics.js +86 -0
  28. package/dist/benchmark/simulation-types.js +1 -0
  29. package/dist/dependencies.js +197 -0
  30. package/dist/generation/TrajectoryGenerator.js +244 -0
  31. package/dist/generation/index.js +6 -0
  32. package/dist/huggingface/HuggingFaceDatasetUploader.js +463 -0
  33. package/dist/huggingface/HuggingFaceIntegrationService.js +272 -0
  34. package/dist/huggingface/HuggingFaceModelUploader.js +385 -0
  35. package/dist/huggingface/index.js +9 -0
  36. package/dist/huggingface/shared/HuggingFaceUploadUtil.js +144 -0
  37. package/dist/index.js +41 -0
  38. package/dist/init-training.js +43 -0
  39. package/dist/metrics/TrajectoryMetricsExtractor.js +523 -0
  40. package/dist/metrics/__tests__/TrajectoryMetricsExtractor.test.js +628 -0
  41. package/dist/metrics/index.js +7 -0
  42. package/dist/metrics/types.js +21 -0
  43. package/dist/rubrics/__tests__/index.test.js +150 -0
  44. package/dist/rubrics/ass-kisser.js +83 -0
  45. package/dist/rubrics/degen.js +78 -0
  46. package/dist/rubrics/goody-twoshoes.js +82 -0
  47. package/dist/rubrics/index.js +184 -0
  48. package/dist/rubrics/information-trader.js +82 -0
  49. package/dist/rubrics/infosec.js +99 -0
  50. package/dist/rubrics/liar.js +102 -0
  51. package/dist/rubrics/perps-trader.js +85 -0
  52. package/dist/rubrics/researcher.js +79 -0
  53. package/dist/rubrics/scammer.js +80 -0
  54. package/dist/rubrics/social-butterfly.js +71 -0
  55. package/dist/rubrics/super-predictor.js +95 -0
  56. package/dist/rubrics/trader.js +65 -0
  57. package/dist/scoring/ArchetypeScoringService.js +301 -0
  58. package/dist/scoring/JudgePromptBuilder.js +401 -0
  59. package/dist/scoring/LLMJudgeCache.js +263 -0
  60. package/dist/scoring/index.js +8 -0
  61. package/dist/training/AutomationPipeline.js +714 -0
  62. package/dist/training/BenchmarkService.js +370 -0
  63. package/dist/training/ConfigValidator.js +153 -0
  64. package/dist/training/MarketOutcomesTracker.js +142 -0
  65. package/dist/training/ModelDeployer.js +128 -0
  66. package/dist/training/ModelFetcher.js +48 -0
  67. package/dist/training/ModelSelectionService.js +248 -0
  68. package/dist/training/ModelUsageVerifier.js +106 -0
  69. package/dist/training/MultiModelOrchestrator.js +349 -0
  70. package/dist/training/RLModelConfig.js +295 -0
  71. package/dist/training/RewardBackpropagationService.js +117 -0
  72. package/dist/training/RulerScoringService.js +450 -0
  73. package/dist/training/TrainingMonitor.js +108 -0
  74. package/dist/training/TrajectoryRecorder.js +281 -0
  75. package/dist/training/__tests__/TrajectoryRecorder.test.js +363 -0
  76. package/dist/training/index.js +30 -0
  77. package/dist/training/logRLConfig.js +29 -0
  78. package/dist/training/pipeline.js +80 -0
  79. package/dist/training/storage/ModelStorageService.js +190 -0
  80. package/dist/training/storage/TrainingDataArchiver.js +136 -0
  81. package/dist/training/storage/index.js +7 -0
  82. package/dist/training/types.js +6 -0
  83. package/dist/training/window-utils.js +100 -0
  84. package/dist/utils/index.js +73 -0
  85. package/dist/utils/logger.js +55 -0
  86. package/dist/utils/snowflake.js +15 -0
  87. package/dist/utils/synthetic-detector.js +67 -0
  88. package/package.json +2 -2
  89. package/research-output/training-runs/training-run-1773742857616.json +38 -0
  90. package/research-output/training-runs/training-run-1773742946977.json +38 -0
  91. package/research-output/training-runs/training-run-1773743278891.json +38 -0
  92. package/research-output/training-runs/training-run-1773743409754.json +38 -0
  93. package/research-output/training-runs/training-run-1773743651086.json +38 -0
  94. package/research-output/training-runs/training-run-1773743782883.json +38 -0
@@ -0,0 +1,29 @@
1
+ /**
2
+ * RL Configuration Logger
3
+ *
4
+ * Logs RL model configuration and availability on server startup.
5
+ * Used for diagnostics and verification during deployment.
6
+ */
7
+ import { isRLModelAvailable, logRLModelConfig } from "./RLModelConfig";
8
+ /**
9
+ * Log RL model configuration and verify setup
10
+ *
11
+ * Call this on server startup to display configuration details
12
+ * and verify that the RL training system is properly configured.
13
+ */
14
+ export async function logRLConfigOnStartup() {
15
+ console.log("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
16
+ console.log("🚀 RL Training System Configuration");
17
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
18
+ // Log RL configuration
19
+ logRLModelConfig();
20
+ // Check if RL models are available
21
+ const available = isRLModelAvailable();
22
+ if (available) {
23
+ console.log("\n✅ RL Model system available");
24
+ }
25
+ else {
26
+ console.log("\nℹ️ RL models not available - using base model");
27
+ }
28
+ console.log("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
29
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Training Pipeline – public helpers
3
+ *
4
+ * IMPORTANT: All heavy modules (AutomationPipeline, ModelDeployer) are loaded
5
+ * lazily so that importing this file does NOT trigger a database connection.
6
+ * Consumers that only need types or lightweight utilities can import
7
+ * "@elizaos/training" without side-effects.
8
+ */
9
+ // ---------------------------------------------------------------------------
10
+ // Lazy singletons – only resolved on first call to avoid DB side-effects at
11
+ // module-load time.
12
+ // ---------------------------------------------------------------------------
13
+ let _pipeline = null;
14
+ async function getPipeline() {
15
+ if (!_pipeline) {
16
+ const mod = await import("./AutomationPipeline");
17
+ _pipeline = mod.automationPipeline;
18
+ }
19
+ return _pipeline;
20
+ }
21
+ async function getDeployer() {
22
+ const mod = await import("./ModelDeployer");
23
+ return mod.modelDeployer;
24
+ }
25
+ /**
26
+ * Check whether the current trajectory set is ready for training.
27
+ */
28
+ export async function checkTrainingReadiness() {
29
+ const pipeline = await getPipeline();
30
+ return pipeline.checkTrainingReadiness();
31
+ }
32
+ /**
33
+ * Trigger a new training job.
34
+ */
35
+ export async function triggerTraining(options = {}) {
36
+ const pipeline = await getPipeline();
37
+ return pipeline.triggerTraining(options);
38
+ }
39
+ /**
40
+ * Monitor a training batch by its batch id.
41
+ */
42
+ export async function monitorTrainingJob(batchId) {
43
+ const pipeline = await getPipeline();
44
+ return pipeline.monitorTraining(batchId);
45
+ }
46
+ /**
47
+ * Get summarized status for automation, jobs, and model health.
48
+ */
49
+ export async function getAutomationPipelineStatus() {
50
+ const pipeline = await getPipeline();
51
+ return pipeline.getStatus();
52
+ }
53
+ /**
54
+ * Get model-selection metadata for the next run.
55
+ */
56
+ export async function getNextTrainingModelSelection() {
57
+ const pipeline = await getPipeline();
58
+ return pipeline.getModelSelectionInfo();
59
+ }
60
+ /**
61
+ * Run benchmark and deploy the model only if it passes thresholds.
62
+ */
63
+ export async function benchmarkAndMaybeDeployModel(batchId, autoDeploy = true) {
64
+ const pipeline = await getPipeline();
65
+ return pipeline.benchmarkAndDeploy(batchId, autoDeploy);
66
+ }
67
+ /**
68
+ * Deploy a specific model version using the deployment strategy options.
69
+ */
70
+ export async function deployModelVersion(options) {
71
+ const deployer = await getDeployer();
72
+ return deployer.deploy(options);
73
+ }
74
+ /**
75
+ * Roll back from one version to another.
76
+ */
77
+ export async function rollbackModelVersion(currentVersion, targetVersion) {
78
+ const deployer = await getDeployer();
79
+ return deployer.rollback(currentVersion, targetVersion);
80
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Model Storage Service (Vercel Blob)
3
+ *
4
+ * Handles model versioning and storage using Vercel Blob.
5
+ * Stores trained models with metadata for easy deployment.
6
+ */
7
+ import fs from "node:fs/promises";
8
+ import path from "node:path";
9
+ import { del, list, put } from "@vercel/blob";
10
+ import { getTrainingDataAdapter } from "../../adapter";
11
+ import { logger } from "../../utils/logger";
12
+ export class ModelStorageService {
13
+ blobPrefix = "models/";
14
+ /**
15
+ * Upload trained model to Vercel Blob
16
+ */
17
+ async uploadModel(options) {
18
+ logger.info("Uploading model to Vercel Blob", {
19
+ version: options.version,
20
+ path: options.modelPath,
21
+ });
22
+ // Read model file
23
+ const modelData = await fs.readFile(options.modelPath);
24
+ const fileName = path.basename(options.modelPath);
25
+ // Upload to Vercel Blob
26
+ const blob = await put(`${this.blobPrefix}${options.version}/${fileName}`, modelData, {
27
+ access: "public", // Models can be publicly downloaded
28
+ addRandomSuffix: false,
29
+ });
30
+ // Upload metadata
31
+ await put(`${this.blobPrefix}${options.version}/metadata.json`, JSON.stringify(options.metadata || {}, null, 2), {
32
+ access: "public",
33
+ addRandomSuffix: false,
34
+ });
35
+ logger.info("Model uploaded to Vercel Blob", {
36
+ version: options.version,
37
+ url: blob.url,
38
+ size: blob.size || 0,
39
+ });
40
+ const metadataModelIdPrefix = typeof options.metadata?.modelIdPrefix === "string"
41
+ ? options.metadata.modelIdPrefix
42
+ : undefined;
43
+ const modelIdPrefix = options.modelIdPrefix ||
44
+ metadataModelIdPrefix ||
45
+ process.env.TRAINING_MODEL_ID_PREFIX ||
46
+ "eliza-agent";
47
+ // Save to database via adapter
48
+ const adapter = getTrainingDataAdapter();
49
+ await adapter.insertModel({
50
+ id: `model-${Date.now()}`,
51
+ modelId: `${modelIdPrefix}-${options.version}`,
52
+ version: options.version,
53
+ baseModel: options.metadata?.baseModel || "unsloth/Qwen3-4B-128K",
54
+ trainingBatch: options.metadata?.trainingBatch || null,
55
+ status: "ready",
56
+ deployedAt: null,
57
+ archivedAt: null,
58
+ storagePath: blob.url,
59
+ benchmarkScore: null,
60
+ accuracy: options.metadata?.accuracy || null,
61
+ avgReward: options.metadata?.avgReward || null,
62
+ evalMetrics: null,
63
+ wandbRunId: null,
64
+ wandbArtifactId: null,
65
+ huggingFaceRepo: null,
66
+ agentsUsing: 0,
67
+ totalInferences: 0,
68
+ lastBenchmarked: null,
69
+ benchmarkCount: 0,
70
+ updatedAt: new Date(),
71
+ });
72
+ return {
73
+ version: options.version,
74
+ baseModel: options.metadata?.baseModel || "unsloth/Qwen3-4B-128K",
75
+ blobUrl: blob.url,
76
+ size: blob.size || 0,
77
+ uploadedAt: new Date(),
78
+ metadata: options.metadata || {},
79
+ };
80
+ }
81
+ /**
82
+ * Download model from Vercel Blob
83
+ */
84
+ async downloadModel(version) {
85
+ const adapter = getTrainingDataAdapter();
86
+ const model = await adapter.getModelByVersion(version);
87
+ if (!model) {
88
+ throw new Error(`Model version ${version} not found`);
89
+ }
90
+ // Download model file
91
+ const modelResponse = await fetch(model.storagePath);
92
+ const modelData = Buffer.from(await modelResponse.arrayBuffer());
93
+ // Download metadata
94
+ const metadataUrl = model.storagePath.replace(/\/[^/]+$/, "/metadata.json");
95
+ const metadataResponse = await fetch(metadataUrl);
96
+ const metadata = (await metadataResponse.json());
97
+ return {
98
+ modelData,
99
+ metadata,
100
+ };
101
+ }
102
+ /**
103
+ * List all model versions
104
+ */
105
+ async listModels() {
106
+ const { blobs } = await list({
107
+ prefix: this.blobPrefix,
108
+ });
109
+ const versions = new Map();
110
+ for (const blob of blobs) {
111
+ const parts = blob.pathname.split("/");
112
+ const version = parts[1];
113
+ if (!version)
114
+ continue;
115
+ if (!versions.has(version)) {
116
+ versions.set(version, {
117
+ version,
118
+ blobs: [],
119
+ });
120
+ }
121
+ // Convert uploadedAt to string if it's a Date
122
+ const blobInfo = {
123
+ ...blob,
124
+ uploadedAt: blob.uploadedAt instanceof Date
125
+ ? blob.uploadedAt.toISOString()
126
+ : blob.uploadedAt,
127
+ };
128
+ versions.get(version)?.blobs.push(blobInfo);
129
+ }
130
+ // Get metadata for each version
131
+ const models = [];
132
+ for (const [version, data] of versions) {
133
+ const modelBlob = data.blobs.find((b) => b.pathname.endsWith(".safetensors") || b.pathname.endsWith(".bin"));
134
+ if (modelBlob) {
135
+ // Try to get metadata
136
+ let metadata = {};
137
+ try {
138
+ const metadataBlob = data.blobs.find((b) => b.pathname.endsWith("metadata.json"));
139
+ if (metadataBlob) {
140
+ const response = await fetch(metadataBlob.url);
141
+ metadata = (await response.json());
142
+ }
143
+ }
144
+ catch {
145
+ // No metadata, use defaults
146
+ }
147
+ models.push({
148
+ version,
149
+ baseModel: metadata.baseModel || "unknown",
150
+ blobUrl: modelBlob.url,
151
+ size: modelBlob.size,
152
+ uploadedAt: modelBlob.uploadedAt instanceof Date
153
+ ? modelBlob.uploadedAt
154
+ : new Date(modelBlob.uploadedAt),
155
+ metadata,
156
+ });
157
+ }
158
+ }
159
+ return models.sort((a, b) => b.uploadedAt.getTime() - a.uploadedAt.getTime());
160
+ }
161
+ /**
162
+ * Delete model version
163
+ */
164
+ async deleteModel(version) {
165
+ const { blobs } = await list({
166
+ prefix: `${this.blobPrefix}${version}/`,
167
+ });
168
+ for (const blob of blobs) {
169
+ await del(blob.url);
170
+ }
171
+ // Update database via adapter
172
+ const adapter = getTrainingDataAdapter();
173
+ const model = await adapter.getModelByVersion(version);
174
+ if (model) {
175
+ await adapter.updateModelStatus(model.modelId, "archived", {
176
+ archivedAt: new Date(),
177
+ });
178
+ }
179
+ logger.info("Model deleted from Vercel Blob", { version });
180
+ }
181
+ /**
182
+ * Get latest model version
183
+ */
184
+ async getLatestVersion() {
185
+ const models = await this.listModels();
186
+ return models[0] || null;
187
+ }
188
+ }
189
+ // Singleton
190
+ export const modelStorage = new ModelStorageService();
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Training Data Archiver (Vercel Blob)
3
+ *
4
+ * Archives training data (exported trajectories, RULER scores) to Vercel Blob
5
+ * for long-term storage and reproducibility.
6
+ */
7
+ import fs from "node:fs/promises";
8
+ import path from "node:path";
9
+ import { del, list, put } from "@vercel/blob";
10
+ import { logger } from "../../utils/logger";
11
+ export class TrainingDataArchiver {
12
+ blobPrefix = "training-data/";
13
+ /**
14
+ * Archive training data for a window
15
+ */
16
+ async archiveWindow(options) {
17
+ logger.info("Archiving training data", { windowId: options.windowId });
18
+ const prefix = `${this.blobPrefix}${options.windowId}/`;
19
+ const urls = {
20
+ trajectories: "",
21
+ metadata: "",
22
+ };
23
+ let totalSize = 0;
24
+ // Upload trajectories
25
+ const trajData = await fs.readFile(options.trajectoriesPath);
26
+ const trajBlob = await put(`${prefix}trajectories.jsonl`, trajData, {
27
+ access: "public",
28
+ addRandomSuffix: false,
29
+ });
30
+ urls.trajectories = trajBlob.url;
31
+ totalSize += trajData.length;
32
+ // Upload groups if provided
33
+ if (options.groupsPath) {
34
+ const groupsData = await fs.readFile(options.groupsPath);
35
+ const groupsBlob = await put(`${prefix}groups.jsonl`, groupsData, {
36
+ access: "public",
37
+ addRandomSuffix: false,
38
+ });
39
+ urls.groups = groupsBlob.url;
40
+ totalSize += groupsData.length;
41
+ }
42
+ // Upload RULER scores if provided
43
+ if (options.rulerScoresPath) {
44
+ const scoresData = await fs.readFile(options.rulerScoresPath);
45
+ const scoresBlob = await put(`${prefix}ruler_scores.json`, scoresData, {
46
+ access: "public",
47
+ addRandomSuffix: false,
48
+ });
49
+ urls.rulerScores = scoresBlob.url;
50
+ totalSize += scoresData.length;
51
+ }
52
+ // Upload metadata
53
+ const metadataJson = JSON.stringify(options.metadata || {}, null, 2);
54
+ const metadataBlob = await put(`${prefix}metadata.json`, metadataJson, {
55
+ access: "public",
56
+ addRandomSuffix: false,
57
+ });
58
+ urls.metadata = metadataBlob.url;
59
+ totalSize += Buffer.byteLength(metadataJson, "utf8");
60
+ logger.info("Training data archived", {
61
+ windowId: options.windowId,
62
+ size: totalSize,
63
+ });
64
+ return {
65
+ windowId: options.windowId,
66
+ trajectoryCount: options.metadata?.trajectoryCount || 0,
67
+ blobUrls: urls,
68
+ archivedAt: new Date(),
69
+ size: totalSize,
70
+ };
71
+ }
72
+ /**
73
+ * Retrieve archived training data
74
+ */
75
+ async getWindowData(windowId) {
76
+ const prefix = `${this.blobPrefix}${windowId}/`;
77
+ const { blobs } = await list({ prefix });
78
+ if (blobs.length === 0) {
79
+ return null;
80
+ }
81
+ const result = {};
82
+ for (const blob of blobs) {
83
+ const response = await fetch(blob.url);
84
+ const filename = path.basename(blob.pathname);
85
+ if (filename === "trajectories.jsonl") {
86
+ result.trajectories = await response.text();
87
+ }
88
+ else if (filename === "groups.jsonl") {
89
+ result.groups = await response.text();
90
+ }
91
+ else if (filename === "ruler_scores.json") {
92
+ result.rulerScores = (await response.json());
93
+ }
94
+ else if (filename === "metadata.json") {
95
+ result.metadata = (await response.json());
96
+ }
97
+ }
98
+ // Ensure required fields are present
99
+ if (!result.trajectories || !result.metadata) {
100
+ return null;
101
+ }
102
+ return {
103
+ trajectories: result.trajectories,
104
+ groups: result.groups,
105
+ rulerScores: result.rulerScores,
106
+ metadata: result.metadata,
107
+ };
108
+ }
109
+ /**
110
+ * List all archived windows
111
+ */
112
+ async listWindows() {
113
+ const { blobs } = await list({ prefix: this.blobPrefix });
114
+ const windows = new Set();
115
+ for (const blob of blobs) {
116
+ const parts = blob.pathname.split("/");
117
+ if (parts[1]) {
118
+ windows.add(parts[1]);
119
+ }
120
+ }
121
+ return Array.from(windows).sort().reverse();
122
+ }
123
+ /**
124
+ * Delete archived window
125
+ */
126
+ async deleteWindow(windowId) {
127
+ const prefix = `${this.blobPrefix}${windowId}/`;
128
+ const { blobs } = await list({ prefix });
129
+ for (const blob of blobs) {
130
+ await del(blob.url);
131
+ }
132
+ logger.info("Deleted archived window", { windowId });
133
+ }
134
+ }
135
+ // Singleton
136
+ export const trainingDataArchiver = new TrainingDataArchiver();
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Storage Module
3
+ *
4
+ * Services for storing models and training data to Vercel Blob.
5
+ */
6
+ export { ModelStorageService, modelStorage, } from "./ModelStorageService";
7
+ export { TrainingDataArchiver, trainingDataArchiver, } from "./TrainingDataArchiver";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * TypeScript types for Training Pipeline
3
+ *
4
+ * Proper types to replace 'any' usage throughout the training system
5
+ */
6
+ export {};
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Window Utility Functions
3
+ * Helper functions for time-window based RL training
4
+ */
5
+ /**
6
+ * Get current window ID (hourly timestamp)
7
+ * Format: YYYY-MM-DDTHH:00
8
+ *
9
+ * Example: "2025-01-15T10:00"
10
+ */
11
+ export function getCurrentWindowId() {
12
+ const now = new Date();
13
+ // Round down to the start of the current hour
14
+ const windowStart = new Date(Math.floor(now.getTime() / (60 * 60 * 1000)) * (60 * 60 * 1000));
15
+ // Format as ISO string, take first 13 chars + :00
16
+ return `${windowStart.toISOString().slice(0, 13)}:00`;
17
+ }
18
+ /**
19
+ * Get previous window ID (N hours ago)
20
+ *
21
+ * @param offset - How many hours ago (default: 1)
22
+ */
23
+ export function getPreviousWindowId(offset = 1) {
24
+ const now = new Date();
25
+ const windowStart = new Date(Math.floor(now.getTime() / (60 * 60 * 1000)) * (60 * 60 * 1000));
26
+ // Go back N hours
27
+ windowStart.setHours(windowStart.getHours() - offset);
28
+ return `${windowStart.toISOString().slice(0, 13)}:00`;
29
+ }
30
+ /**
31
+ * Parse window ID to Date object
32
+ *
33
+ * @param windowId - Window ID string (YYYY-MM-DDTHH:00)
34
+ */
35
+ export function parseWindowId(windowId) {
36
+ return new Date(windowId);
37
+ }
38
+ /**
39
+ * Check if a window is complete (current time is past window end)
40
+ *
41
+ * @param windowId - Window ID to check
42
+ * @param windowDurationHours - Window duration (default: 1 hour)
43
+ */
44
+ export function isWindowComplete(windowId, windowDurationHours = 1) {
45
+ const windowStart = parseWindowId(windowId);
46
+ const windowEnd = new Date(windowStart.getTime() + windowDurationHours * 60 * 60 * 1000);
47
+ return Date.now() > windowEnd.getTime();
48
+ }
49
+ /**
50
+ * Get window range (start and end times)
51
+ *
52
+ * @param windowId - Window ID
53
+ * @param windowDurationHours - Window duration (default: 1 hour)
54
+ */
55
+ export function getWindowRange(windowId, windowDurationHours = 1) {
56
+ const start = parseWindowId(windowId);
57
+ const end = new Date(start.getTime() + windowDurationHours * 60 * 60 * 1000);
58
+ return { start, end };
59
+ }
60
+ /**
61
+ * Generate list of window IDs for a time range
62
+ *
63
+ * @param startTime - Start time
64
+ * @param endTime - End time
65
+ * @param windowDurationHours - Window duration (default: 1 hour)
66
+ */
67
+ export function generateWindowIds(startTime, endTime, windowDurationHours = 1) {
68
+ const windows = [];
69
+ const windowMs = windowDurationHours * 60 * 60 * 1000;
70
+ // Round start time down to window boundary
71
+ const currentWindowStart = new Date(Math.floor(startTime.getTime() / windowMs) * windowMs);
72
+ while (currentWindowStart.getTime() <= endTime.getTime()) {
73
+ windows.push(`${currentWindowStart.toISOString().slice(0, 13)}:00`);
74
+ currentWindowStart.setTime(currentWindowStart.getTime() + windowMs);
75
+ }
76
+ return windows;
77
+ }
78
+ /**
79
+ * Get window ID for a specific timestamp
80
+ *
81
+ * @param timestamp - Timestamp to get window for
82
+ * @param windowDurationHours - Window duration (default: 1 hour)
83
+ */
84
+ export function getWindowIdForTimestamp(timestamp, windowDurationHours = 1) {
85
+ const windowMs = windowDurationHours * 60 * 60 * 1000;
86
+ const windowStart = new Date(Math.floor(timestamp.getTime() / windowMs) * windowMs);
87
+ return `${windowStart.toISOString().slice(0, 13)}:00`;
88
+ }
89
+ /**
90
+ * Check if a timestamp falls within a window
91
+ *
92
+ * @param timestamp - Timestamp to check
93
+ * @param windowId - Window ID
94
+ * @param windowDurationHours - Window duration (default: 1 hour)
95
+ */
96
+ export function isTimestampInWindow(timestamp, windowId, windowDurationHours = 1) {
97
+ const { start, end } = getWindowRange(windowId, windowDurationHours);
98
+ const time = timestamp.getTime();
99
+ return time >= start.getTime() && time < end.getTime();
100
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Training Package Utilities
3
+ */
4
+ export { logger } from "./logger";
5
+ export { generateSnowflakeId } from "./snowflake";
6
+ export { assertHasLLMCalls, validateLLMCalls } from "./synthetic-detector";
7
+ /**
8
+ * Split an array into batches of a specified size
9
+ *
10
+ * @param items - Array to split
11
+ * @param batchSize - Maximum size of each batch
12
+ * @returns Array of batches
13
+ */
14
+ export function splitIntoBatches(items, batchSize) {
15
+ const batches = [];
16
+ for (let i = 0; i < items.length; i += batchSize) {
17
+ batches.push(items.slice(i, i + batchSize));
18
+ }
19
+ return batches;
20
+ }
21
+ /**
22
+ * Calculate statistics for an array of numbers
23
+ *
24
+ * @param values - Array of numbers
25
+ * @returns Statistics object with mean, median, std, min, max
26
+ */
27
+ export function calculateArrayStats(values) {
28
+ if (values.length === 0) {
29
+ return { mean: 0, median: 0, std: 0, min: 0, max: 0 };
30
+ }
31
+ const sorted = [...values].sort((a, b) => a - b);
32
+ const mean = values.reduce((a, b) => a + b, 0) / values.length;
33
+ const median = sorted[Math.floor(values.length / 2)] ?? 0;
34
+ const variance = values.reduce((sum, val) => sum + (val - mean) ** 2, 0) / values.length;
35
+ const std = Math.sqrt(variance);
36
+ const min = sorted[0] ?? 0;
37
+ const max = sorted[sorted.length - 1] ?? 0;
38
+ return { mean, median, std, min, max };
39
+ }
40
+ /**
41
+ * Format a decimal value as a percentage string
42
+ *
43
+ * @param value - Decimal value (e.g., 0.75 for 75%)
44
+ * @param decimals - Number of decimal places (default: 1)
45
+ * @returns Formatted percentage string (e.g., "75.0%")
46
+ */
47
+ export function formatPercent(value, decimals = 1) {
48
+ return `${(value * 100).toFixed(decimals)}%`;
49
+ }
50
+ /**
51
+ * Format a number as currency string
52
+ *
53
+ * @param value - Number value
54
+ * @param decimals - Number of decimal places (default: 2)
55
+ * @param prefix - Currency prefix (default: "$")
56
+ * @returns Formatted currency string (e.g., "$1,234.56")
57
+ */
58
+ export function formatCurrency(value, decimals = 2, prefix = "$") {
59
+ const sign = value >= 0 ? "" : "-";
60
+ return `${sign}${prefix}${Math.abs(value).toFixed(decimals)}`;
61
+ }
62
+ /**
63
+ * Format a number as currency with explicit sign
64
+ *
65
+ * @param value - Number value
66
+ * @param decimals - Number of decimal places (default: 2)
67
+ * @param prefix - Currency prefix (default: "$")
68
+ * @returns Formatted currency string with sign (e.g., "+$123.45" or "-$67.89")
69
+ */
70
+ export function formatCurrencyWithSign(value, decimals = 2, prefix = "$") {
71
+ const sign = value >= 0 ? "+" : "-";
72
+ return `${sign}${prefix}${Math.abs(value).toFixed(decimals)}`;
73
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Training Package Logger
3
+ *
4
+ * Simple console-based logger for the training package.
5
+ * Provides consistent logging format across all training services.
6
+ */
7
+ function formatData(data) {
8
+ if (data instanceof Error) {
9
+ return data.message;
10
+ }
11
+ if (typeof data === "object" && data !== null) {
12
+ return JSON.stringify(data, null, 2);
13
+ }
14
+ return String(data);
15
+ }
16
+ export const logger = {
17
+ info: (message, data, context) => {
18
+ const prefix = context ? `[${context}] ` : "";
19
+ if (data !== undefined) {
20
+ console.log(`${prefix}[INFO] ${message}`, formatData(data));
21
+ }
22
+ else {
23
+ console.log(`${prefix}[INFO] ${message}`);
24
+ }
25
+ },
26
+ error: (message, data, context) => {
27
+ const prefix = context ? `[${context}] ` : "";
28
+ if (data !== undefined) {
29
+ console.error(`${prefix}[ERROR] ${message}`, formatData(data));
30
+ }
31
+ else {
32
+ console.error(`${prefix}[ERROR] ${message}`);
33
+ }
34
+ },
35
+ warn: (message, data, context) => {
36
+ const prefix = context ? `[${context}] ` : "";
37
+ if (data !== undefined) {
38
+ console.warn(`${prefix}[WARN] ${message}`, formatData(data));
39
+ }
40
+ else {
41
+ console.warn(`${prefix}[WARN] ${message}`);
42
+ }
43
+ },
44
+ debug: (message, data, context) => {
45
+ if (process.env.DEBUG) {
46
+ const prefix = context ? `[${context}] ` : "";
47
+ if (data !== undefined) {
48
+ console.log(`${prefix}[DEBUG] ${message}`, formatData(data));
49
+ }
50
+ else {
51
+ console.log(`${prefix}[DEBUG] ${message}`);
52
+ }
53
+ }
54
+ },
55
+ };