@barivia/barsom-mcp 0.13.0 → 0.14.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 +21 -11
- package/dist/index.js +1 -1
- package/dist/prepare_training_prompt.js +1 -1
- package/dist/shared.js +1 -1
- package/dist/tools/account.js +1 -1
- package/dist/tools/datasets.js +12 -12
- package/dist/tools/inference.js +35 -4
- package/dist/tools/jobs.js +58 -442
- package/dist/tools/results.js +1 -1
- package/dist/tools/train.js +262 -0
- package/dist/tools/training_core.js +185 -0
- package/dist/tools/training_prep.js +1 -1
- package/package.json +1 -1
package/dist/tools/jobs.js
CHANGED
|
@@ -1,48 +1,31 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { registerAuditedTool } from "../audit.js";
|
|
3
3
|
import { apiCall, textResult } from "../shared.js";
|
|
4
|
-
|
|
4
|
+
import { runTrain } from "./train.js";
|
|
5
|
+
import { runBatchPredict } from "./inference.js";
|
|
6
|
+
import { TRAINING_INPUT_SCHEMA } from "./training_core.js";
|
|
7
|
+
export const JOBS_DESCRIPTION_BASE = `Manage and inspect jobs (lifecycle).
|
|
5
8
|
|
|
6
9
|
| Action | Use when |
|
|
7
10
|
|--------|----------|
|
|
8
|
-
| status | Polling after any async
|
|
9
|
-
| list | Finding job IDs
|
|
10
|
-
| compare | Picking the best TRAINING run from a set
|
|
11
|
-
| train_map | Submitting a new map training job — returns job_id for polling |
|
|
12
|
-
| train_impute | Sparse training data: train a map AND impute missing cells in one job (accelerated missSOM) — returns map + imputed.csv |
|
|
13
|
-
| train_siom_map | Submitting a self-interacting map training job — same map flow with SIOM coverage control |
|
|
14
|
-
| train_floop_siom | Submitting a FLooP-SIOM job (growing manifold; default topology=free / CHL) — requires Premium or Enterprise plan (all_algorithms) |
|
|
11
|
+
| status | Polling after any async submission — call every 10–15s |
|
|
12
|
+
| list | Finding job IDs / reviewing pipeline state (optionally filter by dataset_id) |
|
|
13
|
+
| compare | Picking the best TRAINING run from a set (metrics table). NOT inference(compare), which diffs one dataset's distribution against the training set. |
|
|
15
14
|
| cancel | Stopping a running or pending job to free the worker |
|
|
16
15
|
| delete | Permanently removing a job and all its S3 result files |
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
To SUBMIT training, use the **train** tool (train(action=map | siom_map | impute | floop_siom | baseline_study)); to score data, use **inference**. The jobs(action=train_map | train_siom_map | train_impute | train_floop_siom | run_baseline_study | batch_predict) actions still work as **deprecated** forwarders for one release — migrate to train / inference.
|
|
19
18
|
|
|
20
19
|
ASYNC POLLING PROTOCOL (action=status):
|
|
21
20
|
- Poll every 10-15 seconds. Do NOT poll faster — it wastes context.
|
|
22
|
-
- Progress increases through phases (ordering then convergence for map training)
|
|
21
|
+
- Progress increases through phases (ordering then convergence for map training); other job types differ.
|
|
23
22
|
- For large grids (40×40+), do not assume failure before 3 minutes on CPU.
|
|
24
|
-
- Wait for status "completed" before calling results(action=get).
|
|
25
23
|
- Map training typical times: 10×10 ~30s | 20×20 ~3–5 min | 40×40 ~15–30 min.
|
|
24
|
+
- completed → results(action=get); failed → read error + optional failure_stage (preprocessing/training/metrics/visualization/upload): memory error → smaller grid/batch and retrain; column missing → datasets(action=preview); NaN on a standard map → train(action=impute) for sparse columns or clean the CSV.
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
- completed → call results(action=get) to retrieve the map and metrics
|
|
29
|
-
- failed → error message and optional failure_stage (e.g. preprocessing, training, metrics, visualization, upload) indicate which phase broke:
|
|
30
|
-
- memory/allocation error: reduce batch_size or grid size and retrain
|
|
31
|
-
- column missing: verify with datasets(action=preview)
|
|
32
|
-
- NaN error on train_map: use jobs(action=train_impute) for sparse training columns, or clean the CSV for complete-case train_map
|
|
33
|
-
|
|
34
|
-
Parameter details (grid, epochs, batch, model, presets, normalize, categorical_features, backend, FLooP max_nodes/effort, SIOM geometry) live in the **training_guidance** tool / **prepare_training** prompt — call them instead of inlining here. Backend strings are "cpu" | "gpu" | "gpu_graphs" (no colon); on CPU-only hosts pass backend=cpu.
|
|
35
|
-
|
|
36
|
-
action=train_map / train_siom_map: Submits a grid-map training job. Returns job_id — poll with jobs(action=status, job_id=...). train_siom_map adds SIOM coverage control.
|
|
37
|
-
action=train_impute: Missing-tolerant map training (accelerated missSOM / SIOM). Use when many cells are missing across training columns; use inference(impute_column) when you already have a complete-case map and need to fill one held-out column. Plain numeric columns only (no cyclic/temporal/categorical). Defaults model=auto, cv_folds=5 → quality.csv with two labeled numbers per column: kfold_holdout_pool (honest held-out) and resubstitution_optimistic (in-sample, optimistic). status returns progress_phase (ordering/convergence/cv/upload). Artifacts: imputed.csv, imputation_mask.csv, optional imputation_uncertainty.csv. A completed train_impute job_id is a valid parent for inference(impute_column).
|
|
38
|
-
action=train_floop_siom: growing node-budget manifold instead of a fixed hex grid. Requires a plan with all_algorithms (Premium/Enterprise); otherwise the API returns a clear upgrade message. Default topology=free (CHL); topology=chain for a strict 1D backbone. max_nodes is a TOTAL node budget (not grid width/area). If coverage collapses, reduce max_nodes before increasing effort.
|
|
39
|
-
action=compare: metrics table (QE, TE, explained variance, silhouette) for 2+ COMPLETED TRAINING jobs — pick the best run in a sweep. NOT inference(action=compare) (single trained job + second dataset → density-diff heatmap). Only hyperparameter columns that differ across runs are shown; pass label="sweep_a"/"sweep_b" on train_map for readable rows.
|
|
26
|
+
action=compare: metrics table (QE, TE, explained variance, silhouette) for 2+ COMPLETED TRAINING jobs — pick the best run in a sweep. NOT inference(action=compare) (single trained job + second dataset → density-diff heatmap). Only hyperparameter columns that differ across runs are shown; pass label="…" at train time for readable rows.
|
|
40
27
|
action=cancel: Not instant — worker checks between phases. Expect up to 30s delay.
|
|
41
28
|
action=delete: WARNING — job ID will no longer work with results or any other tool.`;
|
|
42
|
-
export async function fetchTrainingPresets() {
|
|
43
|
-
const configData = (await apiCall("GET", "/v1/training/config"));
|
|
44
|
-
return configData?.presets || {};
|
|
45
|
-
}
|
|
46
29
|
/**
|
|
47
30
|
* Render the jobs(compare) markdown table. Strategy: always show job_id (or label
|
|
48
31
|
* if present) + the four core metrics (QE, TE, Expl.Var, Silhouette); add any
|
|
@@ -134,432 +117,81 @@ export function renderCompareTable(comparisons) {
|
|
|
134
117
|
}
|
|
135
118
|
if (varying.length === 0 && ok.length >= 2) {
|
|
136
119
|
lines.push("");
|
|
137
|
-
lines.push("(All hyperparameters identical across compared jobs — only metrics shown. Pass label=\"…\"
|
|
120
|
+
lines.push("(All hyperparameters identical across compared jobs — only metrics shown. Pass label=\"…\" at train time for readable run names.)");
|
|
138
121
|
}
|
|
139
122
|
else if (!anyLabel && ok.length >= 2) {
|
|
140
123
|
lines.push("");
|
|
141
|
-
lines.push("Tip: pass label=\"sweep_xyz\" on
|
|
124
|
+
lines.push("Tip: pass label=\"sweep_xyz\" on train(action=map | siom_map | floop_siom) to make compare rows readable.");
|
|
142
125
|
}
|
|
143
126
|
return lines.join("\n");
|
|
144
127
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (grid_x !== undefined && grid_y !== undefined)
|
|
154
|
-
params.grid = [grid_x, grid_y];
|
|
155
|
-
else if (p)
|
|
156
|
-
params.grid = p.grid;
|
|
157
|
-
if (epochs !== undefined)
|
|
158
|
-
params.epochs = epochs;
|
|
159
|
-
else if (p)
|
|
160
|
-
params.epochs = p.epochs;
|
|
161
|
-
if (cyclic_features?.length)
|
|
162
|
-
params.cyclic_features = cyclic_features;
|
|
163
|
-
if (columns?.length)
|
|
164
|
-
params.columns = columns;
|
|
165
|
-
if (auto_log_transforms)
|
|
166
|
-
params.auto_log_transforms = auto_log_transforms;
|
|
167
|
-
if (time_delay_embeddings?.length)
|
|
168
|
-
params.time_delay_embeddings = time_delay_embeddings;
|
|
169
|
-
if (categorical_features?.length)
|
|
170
|
-
params.categorical_features = categorical_features;
|
|
171
|
-
if (transforms && Object.keys(transforms).length > 0)
|
|
172
|
-
params.transforms = transforms;
|
|
173
|
-
if (temporal_features?.length)
|
|
174
|
-
params.temporal_features = temporal_features;
|
|
175
|
-
if (feature_weights && Object.keys(feature_weights).length > 0)
|
|
176
|
-
params.feature_weights = feature_weights;
|
|
177
|
-
if (sigma_f !== undefined)
|
|
178
|
-
params.sigma_f = sigma_f;
|
|
179
|
-
if (learning_rate !== undefined)
|
|
180
|
-
params.learning_rate = learning_rate;
|
|
181
|
-
if (batch_size !== undefined)
|
|
182
|
-
params.batch_size = batch_size;
|
|
183
|
-
else if (p)
|
|
184
|
-
params.batch_size = p.batch_size;
|
|
185
|
-
if (quality_metrics !== undefined)
|
|
186
|
-
params.quality_metrics = quality_metrics;
|
|
187
|
-
if (backend !== undefined && backend !== "auto")
|
|
188
|
-
params.backend = backend;
|
|
189
|
-
else if (p?.backend)
|
|
190
|
-
params.backend = p.backend;
|
|
191
|
-
params.output_format = output_format ?? "png";
|
|
192
|
-
const dpiMap = { standard: 1, retina: 2, print: 4 };
|
|
193
|
-
if (output_dpi && output_dpi !== "retina")
|
|
194
|
-
params.output_dpi = dpiMap[output_dpi] ?? 2;
|
|
195
|
-
if (colormap)
|
|
196
|
-
params.colormap = colormap;
|
|
197
|
-
if (row_range && row_range.length >= 2 && row_range[0] <= row_range[1])
|
|
198
|
-
params.row_range = row_range;
|
|
199
|
-
if (siom_feature_geometry !== undefined)
|
|
200
|
-
params.siom_feature_geometry = siom_feature_geometry;
|
|
201
|
-
if (siom_qe_backend !== undefined)
|
|
202
|
-
params.siom_qe_backend = siom_qe_backend;
|
|
203
|
-
if (siom_qe_batch_size !== undefined)
|
|
204
|
-
params.siom_qe_batch_size = siom_qe_batch_size;
|
|
205
|
-
const effectiveGrid = params.grid;
|
|
206
|
-
const effectiveEpochs = params.epochs;
|
|
207
|
-
const effectiveBatch = params.batch_size;
|
|
208
|
-
const effectiveBackend = params.backend;
|
|
209
|
-
const paramSummary = [
|
|
210
|
-
effectiveGrid ? `grid=${effectiveGrid[0]}×${effectiveGrid[1]}` : "grid=auto",
|
|
211
|
-
effectiveEpochs ? `epochs=${Array.isArray(effectiveEpochs) ? effectiveEpochs.join("+") : effectiveEpochs}` : "epochs=auto",
|
|
212
|
-
effectiveBatch ? `batch=${effectiveBatch}` : "batch=auto",
|
|
213
|
-
`model=${params.model}`,
|
|
214
|
-
effectiveBackend ? `backend=${effectiveBackend}` : "",
|
|
215
|
-
preset ? `(from preset: ${preset})` : "",
|
|
216
|
-
].filter(Boolean).join(", ");
|
|
217
|
-
return { params, paramSummary, effectiveGrid };
|
|
218
|
-
}
|
|
128
|
+
/** Deprecated jobs(train_*) / run_baseline_study actions -> the train tool's concise actions. */
|
|
129
|
+
const TRAIN_ALIAS = {
|
|
130
|
+
train_map: "map",
|
|
131
|
+
train_siom_map: "siom_map",
|
|
132
|
+
train_impute: "impute",
|
|
133
|
+
train_floop_siom: "floop_siom",
|
|
134
|
+
run_baseline_study: "baseline_study",
|
|
135
|
+
};
|
|
219
136
|
export function registerJobsTool(server, description) {
|
|
220
137
|
registerAuditedTool(server, "jobs", description, {
|
|
221
138
|
action: z
|
|
222
|
-
.enum([
|
|
223
|
-
|
|
139
|
+
.enum([
|
|
140
|
+
"status", "list", "compare", "cancel", "delete",
|
|
141
|
+
// Deprecated forwarders (kept for one release; use train / inference).
|
|
142
|
+
"train_map", "train_siom_map", "train_impute", "train_floop_siom", "run_baseline_study", "batch_predict",
|
|
143
|
+
])
|
|
144
|
+
.describe("status: check progress; list: see all jobs; compare: metrics table; cancel: stop job; delete: remove job + files. DEPRECATED forwarders: train_map/train_siom_map/train_impute/train_floop_siom/run_baseline_study (use the train tool); batch_predict (use inference)."),
|
|
224
145
|
job_id: z
|
|
225
146
|
.string()
|
|
226
147
|
.optional()
|
|
227
|
-
.describe("Job ID — required for action=status, cancel, delete, batch_predict (parent training job)"),
|
|
228
|
-
inputs: z
|
|
229
|
-
.array(z.object({
|
|
230
|
-
dataset_id: z.string().optional(),
|
|
231
|
-
rows: z.array(z.record(z.string(), z.number())).optional(),
|
|
232
|
-
}))
|
|
233
|
-
.optional()
|
|
234
|
-
.describe("action=batch_predict: array of inputs; each item has dataset_id and/or rows (same as inference predict)."),
|
|
148
|
+
.describe("Job ID — required for action=status, cancel, delete, and the deprecated batch_predict (parent training job)."),
|
|
235
149
|
job_ids: z
|
|
236
150
|
.array(z.string())
|
|
237
151
|
.optional()
|
|
238
|
-
.describe("Array of job IDs — required for action=compare (minimum 2)"),
|
|
152
|
+
.describe("Array of job IDs — required for action=compare (minimum 2)."),
|
|
239
153
|
dataset_id: z
|
|
240
154
|
.string()
|
|
241
155
|
.optional()
|
|
242
|
-
.describe("
|
|
156
|
+
.describe("action=list: filter jobs by this dataset ID. Also required by the deprecated train_* forwarders."),
|
|
243
157
|
label: z
|
|
244
158
|
.string()
|
|
245
159
|
.max(120)
|
|
246
160
|
.optional()
|
|
247
|
-
.describe("Optional run label
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
return n;
|
|
259
|
-
const m = v.match(/^\[\s*(\d+)\s*,\s*(\d+)\s*\]$/);
|
|
260
|
-
if (m)
|
|
261
|
-
return [parseInt(m[1], 10), parseInt(m[2], 10)];
|
|
262
|
-
}
|
|
263
|
-
return v;
|
|
264
|
-
}, z.union([z.number().int(), z.array(z.number().int()).length(2)]).optional()),
|
|
265
|
-
model: z.enum(["auto", "SOM", "SIOM", "RSOM", "SOM-SOFT", "RSOM-SOFT"]).optional(),
|
|
266
|
-
periodic: z.boolean().optional().default(true),
|
|
267
|
-
columns: z.array(z.string()).optional(),
|
|
268
|
-
cyclic_features: z.array(z.object({
|
|
269
|
-
feature: z.string(),
|
|
270
|
-
period: z.number(),
|
|
271
|
-
})).optional(),
|
|
272
|
-
temporal_features: z.array(z.object({
|
|
273
|
-
columns: z.array(z.string()),
|
|
274
|
-
format: z.string(),
|
|
275
|
-
extract: z.array(z.enum(["hour_of_day", "day_of_year", "month", "day_of_week", "minute_of_hour"])),
|
|
276
|
-
cyclic: z.boolean().default(true),
|
|
277
|
-
separator: z.string().optional(),
|
|
278
|
-
})).optional(),
|
|
279
|
-
feature_weights: z.record(z.number()).optional(),
|
|
280
|
-
transforms: z.record(z.enum(["log", "log1p", "log10", "sqrt", "square", "abs", "invert", "rank", "none"])).optional(),
|
|
281
|
-
auto_log_transforms: z.boolean().optional().default(false),
|
|
282
|
-
time_delay_embeddings: z.array(z.object({
|
|
283
|
-
feature: z.string(),
|
|
284
|
-
lags: z.array(z.number().int().min(1))
|
|
285
|
-
})).optional(),
|
|
286
|
-
categorical_features: z.array(z.object({
|
|
287
|
-
feature: z.string(),
|
|
288
|
-
categories: z.array(z.string()),
|
|
289
|
-
weight: z.number().positive()
|
|
290
|
-
})).optional(),
|
|
291
|
-
normalize: z.union([z.enum(["all", "auto", "mad", "sigmoidal"]), z.array(z.string())]).optional().default("auto"),
|
|
292
|
-
sigma_f: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
293
|
-
learning_rate: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.union([z.number(), z.object({
|
|
294
|
-
ordering: z.tuple([z.number(), z.number()]),
|
|
295
|
-
convergence: z.tuple([z.number(), z.number()]),
|
|
296
|
-
})]).optional()),
|
|
297
|
-
batch_size: z.number().int().optional(),
|
|
298
|
-
quality_metrics: z.union([z.enum(["fast", "standard", "full"]), z.array(z.string())]).optional(),
|
|
299
|
-
backend: z.enum(["auto", "cpu", "gpu", "gpu_graphs"]).optional().default("auto"),
|
|
300
|
-
output_format: z.enum(["png", "pdf", "svg"]).optional().default("png"),
|
|
301
|
-
output_dpi: z.enum(["standard", "retina", "print"]).optional().default("retina"),
|
|
302
|
-
colormap: z.string().optional(),
|
|
303
|
-
row_range: z.tuple([z.number().int().min(1), z.number().int().min(1)]).optional(),
|
|
304
|
-
cv_folds: z.number().int().min(0).max(20).optional()
|
|
305
|
-
.describe("train_impute only: per-column imputation quality → quality.csv with two labeled methods (kfold_holdout_pool = held-out; resubstitution_optimistic = in-sample). 0=off, default 5."),
|
|
306
|
-
viz_mode: z.enum(["full", "summary", "summary_plus_top"]).optional()
|
|
307
|
-
.describe("Visualization density; auto-capped when feature count > 40"),
|
|
308
|
-
viz_top_components: z.number().int().min(0).max(64).optional()
|
|
309
|
-
.describe("With viz_mode=summary_plus_top: upload top-N component maps by variance (default 8)"),
|
|
310
|
-
emit_cell_uncertainty: z.boolean().optional()
|
|
311
|
-
.describe("train_impute: write imputation_uncertainty.csv (pool_std per imputed cell)"),
|
|
312
|
-
gamma: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
313
|
-
gamma_f: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
314
|
-
siom_decay: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
315
|
-
siom_penalty: z.enum(["linear", "log", "exp"]).optional(),
|
|
316
|
-
penalty_alpha: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
317
|
-
reset_per_epoch: z.boolean().optional(),
|
|
318
|
-
siom_feature_geometry: z.enum(["l2", "mixed", "auto"]).optional().describe("train_siom_map: l2 (default) = legacy L2 on cos/sin columns; mixed = cyclic torus distance when cyclic pairs exist; auto = mixed only when cyclic encodings are present"),
|
|
319
|
-
siom_qe_backend: z.enum(["cpu", "cuda", "auto"]).optional().describe("train_siom_map: backend for metric-aligned siom_qe when using mixed geometry; auto uses GPU when available"),
|
|
320
|
-
siom_qe_batch_size: z.number().int().min(1).max(1_000_000).optional().describe("train_siom_map: batch size for GPU SIOM QE (default 256 on worker)"),
|
|
321
|
-
topology: z.enum(["free", "chain"]).optional().default("free"),
|
|
322
|
-
max_nodes: z.number().int().min(2).optional(),
|
|
323
|
-
effort: z.enum(["quick", "standard", "thorough"]).optional().default("standard"),
|
|
324
|
-
n_initial: z.number().int().min(2).optional(),
|
|
325
|
-
n_passes: z.number().int().min(1).optional(),
|
|
326
|
-
growth_interval: z.number().int().min(1).optional(),
|
|
327
|
-
neighborhood_size: z.number().int().min(1).optional(),
|
|
328
|
-
edge_max_age: z.number().int().min(1).optional(),
|
|
329
|
-
max_degree: z.number().int().min(0).optional(),
|
|
330
|
-
error_threshold: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
331
|
-
error_decay: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
332
|
-
ring_close_threshold: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
333
|
-
sigma_0: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
334
|
-
eta_0: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
335
|
-
eta_f: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
336
|
-
elastic_lambda: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
337
|
-
elastic_mu: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
338
|
-
elastic_anchor: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
339
|
-
anchor_percentile: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
161
|
+
.describe("Optional run label for the deprecated train_* forwarders (≤120 chars; appears in jobs(list) and the jobs(compare) table)."),
|
|
162
|
+
inputs: z
|
|
163
|
+
.array(z.object({
|
|
164
|
+
dataset_id: z.string().optional(),
|
|
165
|
+
rows: z.array(z.record(z.string(), z.number())).optional(),
|
|
166
|
+
}))
|
|
167
|
+
.optional()
|
|
168
|
+
.describe("Deprecated batch_predict forwarder: array of inputs; each item has dataset_id and/or rows."),
|
|
169
|
+
// Deprecated: training parameters accepted only by the jobs(train_*) forwarders.
|
|
170
|
+
// Submit training via the `train` tool instead; these are removed in a later release.
|
|
171
|
+
...TRAINING_INPUT_SCHEMA,
|
|
340
172
|
}, async (args) => {
|
|
341
173
|
const { action, job_id, job_ids, dataset_id } = args;
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
const params = {
|
|
352
|
-
model: "SOM",
|
|
353
|
-
periodic: false,
|
|
354
|
-
normalize: "mad", // robust normalization
|
|
355
|
-
grid: [side, side],
|
|
356
|
-
epochs: [10, 20],
|
|
357
|
-
batch_size: 32,
|
|
358
|
-
quality_metrics: "standard",
|
|
359
|
-
output_format: "png",
|
|
360
|
-
output_dpi: 2,
|
|
361
|
-
colormap: "coolwarm"
|
|
174
|
+
// ---- Deprecated forwarders -> train / inference ----
|
|
175
|
+
const mapped = TRAIN_ALIAS[action];
|
|
176
|
+
if (mapped) {
|
|
177
|
+
const result = await runTrain(mapped, args);
|
|
178
|
+
return {
|
|
179
|
+
content: [
|
|
180
|
+
...result.content,
|
|
181
|
+
{ type: "text", text: `Note: jobs(action=${action}) is deprecated; use train(action=${mapped}).` },
|
|
182
|
+
],
|
|
362
183
|
};
|
|
363
|
-
const data = (await apiCall("POST", "/v1/jobs", { dataset_id, params }));
|
|
364
|
-
const jid = String(data.id ?? "");
|
|
365
|
-
return { content: [{ type: "text", text: `Baseline study submitted. Job ID: ${jid}\nGrid size: ${side}x${side}\nNormalization: MAD\n\nPoll with jobs(action=status, job_id="${jid}") until complete, then retrieve with results(action=get, job_id="${jid}"). Optional: training_monitor(job_id="${jid}") for a visual panel—not required.` }] };
|
|
366
184
|
}
|
|
367
|
-
if (action === "
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
if (e.httpStatus === 401 || e.httpStatus === 403)
|
|
375
|
-
throw e;
|
|
376
|
-
if (preset && grid_x === undefined && epochs === undefined) {
|
|
377
|
-
throw new Error("Could not fetch training config from server, and missing explicit grid/epochs.");
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
if (!dataset_id)
|
|
381
|
-
throw new Error(`jobs(${action}) requires dataset_id`);
|
|
382
|
-
const { params, paramSummary, effectiveGrid } = buildTrainMapParams({
|
|
383
|
-
preset, grid_x, grid_y, epochs, model, periodic, columns, cyclic_features,
|
|
384
|
-
temporal_features, feature_weights, transforms, auto_log_transforms,
|
|
385
|
-
time_delay_embeddings, categorical_features,
|
|
386
|
-
normalize, sigma_f, learning_rate, batch_size, quality_metrics, backend,
|
|
387
|
-
output_format, output_dpi, colormap, row_range,
|
|
388
|
-
}, PRESETS);
|
|
389
|
-
if (action === "train_impute") {
|
|
390
|
-
params._job_type = "train_impute";
|
|
391
|
-
params.model = model ?? "auto";
|
|
392
|
-
params.cv_folds = cv_folds ?? 5;
|
|
393
|
-
if (viz_mode !== undefined)
|
|
394
|
-
params.viz_mode = viz_mode;
|
|
395
|
-
if (viz_top_components !== undefined)
|
|
396
|
-
params.viz_top_components = viz_top_components;
|
|
397
|
-
if (emit_cell_uncertainty !== undefined)
|
|
398
|
-
params.emit_cell_uncertainty = emit_cell_uncertainty;
|
|
399
|
-
// Plain numeric path only — strip unsupported keys if caller passed them
|
|
400
|
-
delete params.cyclic_features;
|
|
401
|
-
delete params.temporal_features;
|
|
402
|
-
delete params.categorical_features;
|
|
403
|
-
delete params.transforms;
|
|
404
|
-
delete params.auto_log_transforms;
|
|
405
|
-
delete params.time_delay_embeddings;
|
|
406
|
-
}
|
|
407
|
-
if (action === "train_siom_map") {
|
|
408
|
-
params._job_type = "train_siom";
|
|
409
|
-
if (gamma !== undefined)
|
|
410
|
-
params.gamma = gamma;
|
|
411
|
-
if (gamma_f !== undefined)
|
|
412
|
-
params.gamma_f = gamma_f;
|
|
413
|
-
if (siom_decay !== undefined)
|
|
414
|
-
params.siom_decay = siom_decay;
|
|
415
|
-
if (siom_penalty !== undefined)
|
|
416
|
-
params.siom_penalty = siom_penalty;
|
|
417
|
-
if (penalty_alpha !== undefined)
|
|
418
|
-
params.penalty_alpha = penalty_alpha;
|
|
419
|
-
if (reset_per_epoch !== undefined)
|
|
420
|
-
params.reset_per_epoch = reset_per_epoch;
|
|
421
|
-
if (siom_feature_geometry !== undefined)
|
|
422
|
-
params.siom_feature_geometry = siom_feature_geometry;
|
|
423
|
-
if (siom_qe_backend !== undefined)
|
|
424
|
-
params.siom_qe_backend = siom_qe_backend;
|
|
425
|
-
if (siom_qe_batch_size !== undefined)
|
|
426
|
-
params.siom_qe_batch_size = siom_qe_batch_size;
|
|
427
|
-
}
|
|
428
|
-
let totalRows = 0;
|
|
429
|
-
try {
|
|
430
|
-
const preview = (await apiCall("GET", `/v1/datasets/${dataset_id}/preview?n_rows=1`));
|
|
431
|
-
totalRows = Number(preview?.total_rows ?? 0);
|
|
432
|
-
}
|
|
433
|
-
catch { /* ignore */ }
|
|
434
|
-
const submitBody = { dataset_id, params };
|
|
435
|
-
if (label && label.trim() !== "")
|
|
436
|
-
submitBody.label = label;
|
|
437
|
-
const data = (await apiCall("POST", "/v1/jobs", submitBody));
|
|
438
|
-
const newJobId = data.id;
|
|
439
|
-
const variantPrefix = action === "train_siom_map" ? "variant=siom"
|
|
440
|
-
: action === "train_impute" ? "variant=train_impute"
|
|
441
|
-
: "variant=som";
|
|
442
|
-
data.effective_params = `${variantPrefix}, ${paramSummary}`;
|
|
443
|
-
try {
|
|
444
|
-
const sys = (await apiCall("GET", "/v1/system/info"));
|
|
445
|
-
const pending = Number(sys.status?.pending_jobs ?? sys.pending_jobs ?? 0);
|
|
446
|
-
const totalEta = Number(sys.training_time_estimates_seconds?.total ??
|
|
447
|
-
(sys.gpu_available ? 45 : 120));
|
|
448
|
-
const waitMinutes = Math.round((pending * totalEta) / 60);
|
|
449
|
-
let msg = `Job submitted (${variantPrefix}, ${paramSummary}). `;
|
|
450
|
-
if (waitMinutes > 1)
|
|
451
|
-
msg += `You are #${pending + 1} in queue. Estimated wait: ~${waitMinutes} min. `;
|
|
452
|
-
msg += `Poll with jobs(action=status, job_id="${newJobId}"). When status is completed, use results(action=get, job_id="${newJobId}") to view the map${action === "train_impute" ? " and imputed.csv" : ""} and metrics.`;
|
|
453
|
-
msg += ` Optional: training_monitor(job_id="${newJobId}") for charts—not required.`;
|
|
454
|
-
if (effectiveGrid && totalRows > 0 && effectiveGrid[0] * effectiveGrid[1] > totalRows * 0.75) {
|
|
455
|
-
msg += ` Note: Grid may be large for ${totalRows} rows (consider grid=auto for fewer dead nodes).`;
|
|
456
|
-
}
|
|
457
|
-
data.message = msg;
|
|
458
|
-
}
|
|
459
|
-
catch {
|
|
460
|
-
let msg = `Job submitted (${variantPrefix}, ${paramSummary}). Poll with jobs(action=status, job_id="${newJobId}"). When status is completed, use results(action=get, job_id="${newJobId}") to view the map${action === "train_impute" ? " and imputed.csv" : ""} and metrics.`;
|
|
461
|
-
msg += ` Optional: training_monitor(job_id="${newJobId}") for charts—not required.`;
|
|
462
|
-
if (effectiveGrid && totalRows > 0 && effectiveGrid[0] * effectiveGrid[1] > totalRows * 0.75) {
|
|
463
|
-
msg += ` Note: Grid may be large for ${totalRows} rows (consider grid=auto for fewer dead nodes).`;
|
|
464
|
-
}
|
|
465
|
-
data.message = msg;
|
|
466
|
-
}
|
|
467
|
-
return textResult(data);
|
|
468
|
-
}
|
|
469
|
-
if (action === "train_floop_siom") {
|
|
470
|
-
const { columns, cyclic_features, temporal_features, feature_weights, transforms, auto_log_transforms, normalize, row_range, output_format, output_dpi, colormap, topology, max_nodes, effort, n_initial, n_passes, growth_interval, neighborhood_size, edge_max_age, max_degree, gamma, siom_decay, siom_penalty, penalty_alpha, error_threshold, error_decay, ring_close_threshold, sigma_0, sigma_f, eta_0, eta_f, elastic_lambda, elastic_mu, elastic_anchor, anchor_percentile, label, } = args;
|
|
471
|
-
if (!dataset_id)
|
|
472
|
-
throw new Error("jobs(train_floop_siom) requires dataset_id");
|
|
473
|
-
const params = {
|
|
474
|
-
_job_type: "train_floop_siom",
|
|
475
|
-
topology,
|
|
476
|
-
effort,
|
|
477
|
-
normalize,
|
|
478
|
-
output_format: output_format ?? "png",
|
|
185
|
+
if (action === "batch_predict") {
|
|
186
|
+
const result = await runBatchPredict({ job_id, inputs: args.inputs });
|
|
187
|
+
return {
|
|
188
|
+
content: [
|
|
189
|
+
...result.content,
|
|
190
|
+
{ type: "text", text: "Note: jobs(action=batch_predict) is deprecated; use inference(action=batch_predict)." },
|
|
191
|
+
],
|
|
479
192
|
};
|
|
480
|
-
if (max_nodes !== undefined)
|
|
481
|
-
params.max_nodes = max_nodes;
|
|
482
|
-
if (columns?.length)
|
|
483
|
-
params.columns = columns;
|
|
484
|
-
if (cyclic_features?.length)
|
|
485
|
-
params.cyclic_features = cyclic_features;
|
|
486
|
-
if (temporal_features?.length)
|
|
487
|
-
params.temporal_features = temporal_features;
|
|
488
|
-
if (feature_weights && Object.keys(feature_weights).length > 0)
|
|
489
|
-
params.feature_weights = feature_weights;
|
|
490
|
-
if (transforms && Object.keys(transforms).length > 0)
|
|
491
|
-
params.transforms = transforms;
|
|
492
|
-
if (auto_log_transforms)
|
|
493
|
-
params.auto_log_transforms = auto_log_transforms;
|
|
494
|
-
if (row_range && row_range.length >= 2 && row_range[0] <= row_range[1])
|
|
495
|
-
params.row_range = row_range;
|
|
496
|
-
const dpiMap = { standard: 1, retina: 2, print: 4 };
|
|
497
|
-
if (output_dpi && output_dpi !== "retina")
|
|
498
|
-
params.output_dpi = dpiMap[output_dpi] ?? 2;
|
|
499
|
-
if (colormap)
|
|
500
|
-
params.colormap = colormap;
|
|
501
|
-
if (n_initial !== undefined)
|
|
502
|
-
params.n_initial = n_initial;
|
|
503
|
-
if (n_passes !== undefined)
|
|
504
|
-
params.n_passes = n_passes;
|
|
505
|
-
if (growth_interval !== undefined)
|
|
506
|
-
params.growth_interval = growth_interval;
|
|
507
|
-
if (neighborhood_size !== undefined)
|
|
508
|
-
params.neighborhood_size = neighborhood_size;
|
|
509
|
-
if (edge_max_age !== undefined)
|
|
510
|
-
params.edge_max_age = edge_max_age;
|
|
511
|
-
if (max_degree !== undefined)
|
|
512
|
-
params.max_degree = max_degree;
|
|
513
|
-
if (gamma !== undefined)
|
|
514
|
-
params.gamma = gamma;
|
|
515
|
-
if (siom_decay !== undefined)
|
|
516
|
-
params.siom_decay = siom_decay;
|
|
517
|
-
if (siom_penalty !== undefined)
|
|
518
|
-
params.siom_penalty = siom_penalty;
|
|
519
|
-
if (penalty_alpha !== undefined)
|
|
520
|
-
params.penalty_alpha = penalty_alpha;
|
|
521
|
-
if (error_threshold !== undefined)
|
|
522
|
-
params.error_threshold = error_threshold;
|
|
523
|
-
if (error_decay !== undefined)
|
|
524
|
-
params.error_decay = error_decay;
|
|
525
|
-
if (ring_close_threshold !== undefined)
|
|
526
|
-
params.ring_close_threshold = ring_close_threshold;
|
|
527
|
-
if (sigma_0 !== undefined)
|
|
528
|
-
params.sigma_0 = sigma_0;
|
|
529
|
-
if (sigma_f !== undefined)
|
|
530
|
-
params.sigma_f = sigma_f;
|
|
531
|
-
if (eta_0 !== undefined)
|
|
532
|
-
params.eta_0 = eta_0;
|
|
533
|
-
if (eta_f !== undefined)
|
|
534
|
-
params.eta_f = eta_f;
|
|
535
|
-
if (elastic_lambda !== undefined)
|
|
536
|
-
params.elastic_lambda = elastic_lambda;
|
|
537
|
-
if (elastic_mu !== undefined)
|
|
538
|
-
params.elastic_mu = elastic_mu;
|
|
539
|
-
if (elastic_anchor !== undefined)
|
|
540
|
-
params.elastic_anchor = elastic_anchor;
|
|
541
|
-
if (anchor_percentile !== undefined)
|
|
542
|
-
params.anchor_percentile = anchor_percentile;
|
|
543
|
-
const submitBody = { dataset_id, params };
|
|
544
|
-
if (label && label.trim() !== "")
|
|
545
|
-
submitBody.label = label;
|
|
546
|
-
const data = (await apiCall("POST", "/v1/jobs", submitBody));
|
|
547
|
-
const newJobId = data.id;
|
|
548
|
-
const maxNodeSummary = max_nodes === undefined ? "max_nodes=auto(~2*sqrt(n_samples))" : `max_nodes=${max_nodes}`;
|
|
549
|
-
const paramSummary = [
|
|
550
|
-
"variant=floop",
|
|
551
|
-
`topology=${topology ?? "free"}`,
|
|
552
|
-
maxNodeSummary,
|
|
553
|
-
`effort=${effort ?? "standard"}`,
|
|
554
|
-
gamma !== undefined ? `gamma=${gamma}` : "",
|
|
555
|
-
].filter(Boolean).join(", ");
|
|
556
|
-
data.effective_params = paramSummary;
|
|
557
|
-
data.message =
|
|
558
|
-
`Job submitted (${paramSummary}). Poll with jobs(action=status, job_id="${newJobId}"). ` +
|
|
559
|
-
`When status is completed, use results(action=get, job_id="${newJobId}") to view FLooP-SIOM figures and metrics. ` +
|
|
560
|
-
`Optional: training_monitor(job_id="${newJobId}") for charts—not required.`;
|
|
561
|
-
return textResult(data);
|
|
562
193
|
}
|
|
194
|
+
// ---- Lifecycle ----
|
|
563
195
|
if (action === "status") {
|
|
564
196
|
if (!job_id)
|
|
565
197
|
throw new Error("jobs(status) requires job_id");
|
|
@@ -615,22 +247,6 @@ export function registerJobsTool(server, description) {
|
|
|
615
247
|
const data = await apiCall("DELETE", `/v1/jobs/${job_id}`);
|
|
616
248
|
return textResult(data);
|
|
617
249
|
}
|
|
618
|
-
if (action === "batch_predict") {
|
|
619
|
-
if (!job_id)
|
|
620
|
-
throw new Error("jobs(batch_predict) requires job_id (parent training job)");
|
|
621
|
-
if (!args.inputs || !Array.isArray(args.inputs) || args.inputs.length === 0) {
|
|
622
|
-
throw new Error("jobs(batch_predict) requires inputs (non-empty array of { dataset_id? or rows? })");
|
|
623
|
-
}
|
|
624
|
-
const body = { inputs: args.inputs };
|
|
625
|
-
const data = (await apiCall("POST", `/v1/results/${job_id}/batch_predict`, body));
|
|
626
|
-
const ids = (data.ids ?? []);
|
|
627
|
-
return {
|
|
628
|
-
content: [{
|
|
629
|
-
type: "text",
|
|
630
|
-
text: `Batch predict: ${ids.length} job(s) submitted. Parent: ${job_id}. Poll each with jobs(action=status, job_id="<id>"). IDs: ${ids.join(", ")}`,
|
|
631
|
-
}],
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
250
|
throw new Error("Invalid action");
|
|
635
251
|
});
|
|
636
252
|
}
|
package/dist/tools/results.js
CHANGED
|
@@ -307,7 +307,7 @@ NOT FOR: Jobs that haven't completed. Use jobs(action=status) to check first.`,
|
|
|
307
307
|
if (idxs)
|
|
308
308
|
lines.push(`Selected indices: [${idxs.join(", ")}]`);
|
|
309
309
|
}
|
|
310
|
-
lines.push(`The new columns are appended to the source dataset and ready to use in
|
|
310
|
+
lines.push(`The new columns are appended to the source dataset and ready to use in train(action=map, columns=[..., "${outCols[0] ?? "feat_1"}", …]).`);
|
|
311
311
|
content.push({ type: "text", text: lines.join("\n") });
|
|
312
312
|
}
|
|
313
313
|
else if (jobType === "train_floop_siom") {
|