@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.
- package/.turbo/turbo-lint.log +2 -0
- package/.turbo/turbo-typecheck.log +1 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/adapter.js +59 -0
- package/dist/archetypes/ArchetypeConfigService.js +510 -0
- package/dist/archetypes/derive-archetype.js +196 -0
- package/dist/archetypes/index.js +7 -0
- package/dist/benchmark/ArchetypeMatchupBenchmark.js +547 -0
- package/dist/benchmark/BenchmarkChartGenerator.js +632 -0
- package/dist/benchmark/BenchmarkDataGenerator.js +825 -0
- package/dist/benchmark/BenchmarkDataViewer.js +197 -0
- package/dist/benchmark/BenchmarkHistoryService.js +135 -0
- package/dist/benchmark/BenchmarkRunner.js +483 -0
- package/dist/benchmark/BenchmarkValidator.js +158 -0
- package/dist/benchmark/FastEvalRunner.js +133 -0
- package/dist/benchmark/MetricsValidator.js +104 -0
- package/dist/benchmark/MetricsVisualizer.js +775 -0
- package/dist/benchmark/ModelBenchmarkService.js +433 -0
- package/dist/benchmark/ModelRegistry.js +122 -0
- package/dist/benchmark/RulerBenchmarkIntegration.js +168 -0
- package/dist/benchmark/SimulationA2AInterface.js +683 -0
- package/dist/benchmark/SimulationEngine.js +522 -0
- package/dist/benchmark/TaskRunner.js +60 -0
- package/dist/benchmark/__tests__/BenchmarkRunner.test.js +409 -0
- package/dist/benchmark/__tests__/HeadToHead.test.js +105 -0
- package/dist/benchmark/index.js +23 -0
- package/dist/benchmark/parseSimulationMetrics.js +86 -0
- package/dist/benchmark/simulation-types.js +1 -0
- package/dist/dependencies.js +197 -0
- package/dist/generation/TrajectoryGenerator.js +244 -0
- package/dist/generation/index.js +6 -0
- package/dist/huggingface/HuggingFaceDatasetUploader.js +463 -0
- package/dist/huggingface/HuggingFaceIntegrationService.js +272 -0
- package/dist/huggingface/HuggingFaceModelUploader.js +385 -0
- package/dist/huggingface/index.js +9 -0
- package/dist/huggingface/shared/HuggingFaceUploadUtil.js +144 -0
- package/dist/index.js +41 -0
- package/dist/init-training.js +43 -0
- package/dist/metrics/TrajectoryMetricsExtractor.js +523 -0
- package/dist/metrics/__tests__/TrajectoryMetricsExtractor.test.js +628 -0
- package/dist/metrics/index.js +7 -0
- package/dist/metrics/types.js +21 -0
- package/dist/rubrics/__tests__/index.test.js +150 -0
- package/dist/rubrics/ass-kisser.js +83 -0
- package/dist/rubrics/degen.js +78 -0
- package/dist/rubrics/goody-twoshoes.js +82 -0
- package/dist/rubrics/index.js +184 -0
- package/dist/rubrics/information-trader.js +82 -0
- package/dist/rubrics/infosec.js +99 -0
- package/dist/rubrics/liar.js +102 -0
- package/dist/rubrics/perps-trader.js +85 -0
- package/dist/rubrics/researcher.js +79 -0
- package/dist/rubrics/scammer.js +80 -0
- package/dist/rubrics/social-butterfly.js +71 -0
- package/dist/rubrics/super-predictor.js +95 -0
- package/dist/rubrics/trader.js +65 -0
- package/dist/scoring/ArchetypeScoringService.js +301 -0
- package/dist/scoring/JudgePromptBuilder.js +401 -0
- package/dist/scoring/LLMJudgeCache.js +263 -0
- package/dist/scoring/index.js +8 -0
- package/dist/training/AutomationPipeline.js +714 -0
- package/dist/training/BenchmarkService.js +370 -0
- package/dist/training/ConfigValidator.js +153 -0
- package/dist/training/MarketOutcomesTracker.js +142 -0
- package/dist/training/ModelDeployer.js +128 -0
- package/dist/training/ModelFetcher.js +48 -0
- package/dist/training/ModelSelectionService.js +248 -0
- package/dist/training/ModelUsageVerifier.js +106 -0
- package/dist/training/MultiModelOrchestrator.js +349 -0
- package/dist/training/RLModelConfig.js +295 -0
- package/dist/training/RewardBackpropagationService.js +117 -0
- package/dist/training/RulerScoringService.js +450 -0
- package/dist/training/TrainingMonitor.js +108 -0
- package/dist/training/TrajectoryRecorder.js +281 -0
- package/dist/training/__tests__/TrajectoryRecorder.test.js +363 -0
- package/dist/training/index.js +30 -0
- package/dist/training/logRLConfig.js +29 -0
- package/dist/training/pipeline.js +80 -0
- package/dist/training/storage/ModelStorageService.js +190 -0
- package/dist/training/storage/TrainingDataArchiver.js +136 -0
- package/dist/training/storage/index.js +7 -0
- package/dist/training/types.js +6 -0
- package/dist/training/window-utils.js +100 -0
- package/dist/utils/index.js +73 -0
- package/dist/utils/logger.js +55 -0
- package/dist/utils/snowflake.js +15 -0
- package/dist/utils/synthetic-detector.js +67 -0
- package/package.json +2 -2
- package/research-output/training-runs/training-run-1773742857616.json +38 -0
- package/research-output/training-runs/training-run-1773742946977.json +38 -0
- package/research-output/training-runs/training-run-1773743278891.json +38 -0
- package/research-output/training-runs/training-run-1773743409754.json +38 -0
- package/research-output/training-runs/training-run-1773743651086.json +38 -0
- 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,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
|
+
};
|