@barivia/barsom-mcp 0.11.1 → 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 +24 -15
- 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 +65 -18
- package/dist/tools/explore_map.js +6 -7
- package/dist/tools/inference.js +38 -9
- package/dist/tools/jobs.js +58 -453
- 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 +29 -3
- package/package.json +1 -1
package/dist/tools/jobs.js
CHANGED
|
@@ -1,56 +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
|
|
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) |
|
|
15
|
-
| train_floop_chain | Deprecated alias for train_floop_siom — same behavior; prefer train_floop_siom |
|
|
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. |
|
|
16
14
|
| cancel | Stopping a running or pending job to free the worker |
|
|
17
15
|
| delete | Permanently removing a job and all its S3 result files |
|
|
18
16
|
|
|
19
|
-
|
|
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.
|
|
20
18
|
|
|
21
19
|
ASYNC POLLING PROTOCOL (action=status):
|
|
22
20
|
- Poll every 10-15 seconds. Do NOT poll faster — it wastes context.
|
|
23
|
-
- 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.
|
|
24
22
|
- For large grids (40×40+), do not assume failure before 3 minutes on CPU.
|
|
25
|
-
- Wait for status "completed" before calling results(action=get).
|
|
26
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.
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
- completed → call results(action=get) to retrieve the map and metrics
|
|
30
|
-
- failed → error message and optional failure_stage (e.g. preprocessing, training, metrics, visualization, upload) indicate which phase broke:
|
|
31
|
-
- memory/allocation error: reduce batch_size or grid size and retrain
|
|
32
|
-
- column missing: verify with datasets(action=preview)
|
|
33
|
-
- NaN error on train_map: use jobs(action=train_impute) for sparse training columns, or clean the CSV for complete-case train_map
|
|
34
|
-
|
|
35
|
-
action=train_map / train_siom_map: Submits a grid-map training job. Returns job_id — poll with jobs(action=status, job_id=...).
|
|
36
|
-
action=train_impute: Submits missing-tolerant map training (accelerated missSOM / SIOM missSOM). 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 (SIOM on som_siom+ plans), cv_folds=5 → quality.csv. quality.csv reports two clearly-labeled numbers per column: kfold_holdout_pool (honest held-out) and resubstitution_optimistic (in-sample, optimistic). status returns progress_phase (e.g. ordering/convergence/cv/upload). High-dimensional datasets automatically adjust visualization density, metric set, and backend (call training_guidance for specifics). Params: viz_mode, viz_top_components, emit_cell_uncertainty, quality_metrics. Artifacts: imputed.csv, imputation_mask.csv, optional imputation_uncertainty.csv. Completed train_impute job_id is a valid parent for inference(impute_column).
|
|
37
|
-
Presets: quick | standard | refined | high_res — use preset=... for grid/epochs/batch defaults; call training_guidance for details.
|
|
38
|
-
Presets refined/high_res may use GPU. On CPU-only hosts pass backend=cpu. API expects strings "cpu" | "gpu" | "gpu_graphs" (no colon). Future backends (e.g. non-CUDA) may be added under the same contract.
|
|
39
|
-
normalize: "auto" (default) = scale only non-cyclic features; "all" = scale every feature. Use "auto" when using cyclic_features.
|
|
40
|
-
categorical_features: optional baseline categorical support using explicit weighted one-hot encoding. Provide the raw column name, allowed categories, and a weight. Advanced categorical embeddings are intentionally outside this default tool surface.
|
|
41
|
-
label (optional, ≤120 chars): user-supplied run name; appears in jobs(list) and jobs(compare) output. Strongly recommended for sweeps so compare rows stay readable.
|
|
42
|
-
train_siom_map only: siom_feature_geometry l2 (default) | mixed | auto — torus distance on cyclic (cos,sin) pairs when mixed or auto with cyclic encodings. siom_qe_backend cpu|cuda|auto and siom_qe_batch_size align siom_qe with training geometry when mixed.
|
|
43
|
-
action=train_floop_siom (preferred) or train_floop_chain (deprecated alias): Submits FLooP-SIOM — a growing node-budget manifold instead of a fixed hex grid. **Only if the API key’s plan includes all_algorithms** (Premium or Enterprise); otherwise the API returns a clear upgrade message. Default topology is free (CHL dynamic graph); topology=chain is optional for a strict 1D linked-list backbone.
|
|
44
|
-
Key params: topology (default free), max_nodes, effort, gamma, siom_decay.
|
|
45
|
-
max_nodes is a total node budget, not a grid width or area. If omitted, the backend uses a dataset-size heuristic (roughly 2*sqrt(n_samples), capped for stability).
|
|
46
|
-
effort controls passes only: quick≈15, standard≈30, thorough≈60. If coverage collapses, reduce max_nodes before increasing effort.
|
|
47
|
-
action=compare: Returns a metrics table (QE, TE, explained variance, silhouette) for 2+ jobs. The table only shows hyperparameter columns that actually differ across the compared runs (preset, backend, batch_size, normalize, periodic, n_features, etc.) so multi-run sweeps stay readable. Tip: pass label="sweep_a" / "sweep_b" on train_map to make rows self-explanatory.
|
|
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.
|
|
48
27
|
action=cancel: Not instant — worker checks between phases. Expect up to 30s delay.
|
|
49
28
|
action=delete: WARNING — job ID will no longer work with results or any other tool.`;
|
|
50
|
-
export async function fetchTrainingPresets() {
|
|
51
|
-
const configData = (await apiCall("GET", "/v1/training/config"));
|
|
52
|
-
return configData?.presets || {};
|
|
53
|
-
}
|
|
54
29
|
/**
|
|
55
30
|
* Render the jobs(compare) markdown table. Strategy: always show job_id (or label
|
|
56
31
|
* if present) + the four core metrics (QE, TE, Expl.Var, Silhouette); add any
|
|
@@ -142,435 +117,81 @@ export function renderCompareTable(comparisons) {
|
|
|
142
117
|
}
|
|
143
118
|
if (varying.length === 0 && ok.length >= 2) {
|
|
144
119
|
lines.push("");
|
|
145
|
-
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.)");
|
|
146
121
|
}
|
|
147
122
|
else if (!anyLabel && ok.length >= 2) {
|
|
148
123
|
lines.push("");
|
|
149
|
-
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.");
|
|
150
125
|
}
|
|
151
126
|
return lines.join("\n");
|
|
152
127
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (grid_x !== undefined && grid_y !== undefined)
|
|
162
|
-
params.grid = [grid_x, grid_y];
|
|
163
|
-
else if (p)
|
|
164
|
-
params.grid = p.grid;
|
|
165
|
-
if (epochs !== undefined)
|
|
166
|
-
params.epochs = epochs;
|
|
167
|
-
else if (p)
|
|
168
|
-
params.epochs = p.epochs;
|
|
169
|
-
if (cyclic_features?.length)
|
|
170
|
-
params.cyclic_features = cyclic_features;
|
|
171
|
-
if (columns?.length)
|
|
172
|
-
params.columns = columns;
|
|
173
|
-
if (auto_log_transforms)
|
|
174
|
-
params.auto_log_transforms = auto_log_transforms;
|
|
175
|
-
if (time_delay_embeddings?.length)
|
|
176
|
-
params.time_delay_embeddings = time_delay_embeddings;
|
|
177
|
-
if (categorical_features?.length)
|
|
178
|
-
params.categorical_features = categorical_features;
|
|
179
|
-
if (transforms && Object.keys(transforms).length > 0)
|
|
180
|
-
params.transforms = transforms;
|
|
181
|
-
if (temporal_features?.length)
|
|
182
|
-
params.temporal_features = temporal_features;
|
|
183
|
-
if (feature_weights && Object.keys(feature_weights).length > 0)
|
|
184
|
-
params.feature_weights = feature_weights;
|
|
185
|
-
if (sigma_f !== undefined)
|
|
186
|
-
params.sigma_f = sigma_f;
|
|
187
|
-
if (learning_rate !== undefined)
|
|
188
|
-
params.learning_rate = learning_rate;
|
|
189
|
-
if (batch_size !== undefined)
|
|
190
|
-
params.batch_size = batch_size;
|
|
191
|
-
else if (p)
|
|
192
|
-
params.batch_size = p.batch_size;
|
|
193
|
-
if (quality_metrics !== undefined)
|
|
194
|
-
params.quality_metrics = quality_metrics;
|
|
195
|
-
if (backend !== undefined && backend !== "auto")
|
|
196
|
-
params.backend = backend;
|
|
197
|
-
else if (p?.backend)
|
|
198
|
-
params.backend = p.backend;
|
|
199
|
-
params.output_format = output_format ?? "png";
|
|
200
|
-
const dpiMap = { standard: 1, retina: 2, print: 4 };
|
|
201
|
-
if (output_dpi && output_dpi !== "retina")
|
|
202
|
-
params.output_dpi = dpiMap[output_dpi] ?? 2;
|
|
203
|
-
if (colormap)
|
|
204
|
-
params.colormap = colormap;
|
|
205
|
-
if (row_range && row_range.length >= 2 && row_range[0] <= row_range[1])
|
|
206
|
-
params.row_range = row_range;
|
|
207
|
-
if (siom_feature_geometry !== undefined)
|
|
208
|
-
params.siom_feature_geometry = siom_feature_geometry;
|
|
209
|
-
if (siom_qe_backend !== undefined)
|
|
210
|
-
params.siom_qe_backend = siom_qe_backend;
|
|
211
|
-
if (siom_qe_batch_size !== undefined)
|
|
212
|
-
params.siom_qe_batch_size = siom_qe_batch_size;
|
|
213
|
-
const effectiveGrid = params.grid;
|
|
214
|
-
const effectiveEpochs = params.epochs;
|
|
215
|
-
const effectiveBatch = params.batch_size;
|
|
216
|
-
const effectiveBackend = params.backend;
|
|
217
|
-
const paramSummary = [
|
|
218
|
-
effectiveGrid ? `grid=${effectiveGrid[0]}×${effectiveGrid[1]}` : "grid=auto",
|
|
219
|
-
effectiveEpochs ? `epochs=${Array.isArray(effectiveEpochs) ? effectiveEpochs.join("+") : effectiveEpochs}` : "epochs=auto",
|
|
220
|
-
effectiveBatch ? `batch=${effectiveBatch}` : "batch=auto",
|
|
221
|
-
`model=${params.model}`,
|
|
222
|
-
effectiveBackend ? `backend=${effectiveBackend}` : "",
|
|
223
|
-
preset ? `(from preset: ${preset})` : "",
|
|
224
|
-
].filter(Boolean).join(", ");
|
|
225
|
-
return { params, paramSummary, effectiveGrid };
|
|
226
|
-
}
|
|
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
|
+
};
|
|
227
136
|
export function registerJobsTool(server, description) {
|
|
228
137
|
registerAuditedTool(server, "jobs", description, {
|
|
229
138
|
action: z
|
|
230
|
-
.enum([
|
|
231
|
-
|
|
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)."),
|
|
232
145
|
job_id: z
|
|
233
146
|
.string()
|
|
234
147
|
.optional()
|
|
235
|
-
.describe("Job ID — required for action=status, cancel, delete, batch_predict (parent training job)"),
|
|
236
|
-
inputs: z
|
|
237
|
-
.array(z.object({
|
|
238
|
-
dataset_id: z.string().optional(),
|
|
239
|
-
rows: z.array(z.record(z.string(), z.number())).optional(),
|
|
240
|
-
}))
|
|
241
|
-
.optional()
|
|
242
|
-
.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)."),
|
|
243
149
|
job_ids: z
|
|
244
150
|
.array(z.string())
|
|
245
151
|
.optional()
|
|
246
|
-
.describe("Array of job IDs — required for action=compare (minimum 2)"),
|
|
152
|
+
.describe("Array of job IDs — required for action=compare (minimum 2)."),
|
|
247
153
|
dataset_id: z
|
|
248
154
|
.string()
|
|
249
155
|
.optional()
|
|
250
|
-
.describe("
|
|
156
|
+
.describe("action=list: filter jobs by this dataset ID. Also required by the deprecated train_* forwarders."),
|
|
251
157
|
label: z
|
|
252
158
|
.string()
|
|
253
159
|
.max(120)
|
|
254
160
|
.optional()
|
|
255
|
-
.describe("Optional run label
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
return n;
|
|
267
|
-
const m = v.match(/^\[\s*(\d+)\s*,\s*(\d+)\s*\]$/);
|
|
268
|
-
if (m)
|
|
269
|
-
return [parseInt(m[1], 10), parseInt(m[2], 10)];
|
|
270
|
-
}
|
|
271
|
-
return v;
|
|
272
|
-
}, z.union([z.number().int(), z.array(z.number().int()).length(2)]).optional()),
|
|
273
|
-
model: z.enum(["auto", "SOM", "SIOM", "RSOM", "SOM-SOFT", "RSOM-SOFT"]).optional(),
|
|
274
|
-
periodic: z.boolean().optional().default(true),
|
|
275
|
-
columns: z.array(z.string()).optional(),
|
|
276
|
-
cyclic_features: z.array(z.object({
|
|
277
|
-
feature: z.string(),
|
|
278
|
-
period: z.number(),
|
|
279
|
-
})).optional(),
|
|
280
|
-
temporal_features: z.array(z.object({
|
|
281
|
-
columns: z.array(z.string()),
|
|
282
|
-
format: z.string(),
|
|
283
|
-
extract: z.array(z.enum(["hour_of_day", "day_of_year", "month", "day_of_week", "minute_of_hour"])),
|
|
284
|
-
cyclic: z.boolean().default(true),
|
|
285
|
-
separator: z.string().optional(),
|
|
286
|
-
})).optional(),
|
|
287
|
-
feature_weights: z.record(z.number()).optional(),
|
|
288
|
-
transforms: z.record(z.enum(["log", "log1p", "log10", "sqrt", "square", "abs", "invert", "rank", "none"])).optional(),
|
|
289
|
-
auto_log_transforms: z.boolean().optional().default(false),
|
|
290
|
-
time_delay_embeddings: z.array(z.object({
|
|
291
|
-
feature: z.string(),
|
|
292
|
-
lags: z.array(z.number().int().min(1))
|
|
293
|
-
})).optional(),
|
|
294
|
-
categorical_features: z.array(z.object({
|
|
295
|
-
feature: z.string(),
|
|
296
|
-
categories: z.array(z.string()),
|
|
297
|
-
weight: z.number().positive()
|
|
298
|
-
})).optional(),
|
|
299
|
-
normalize: z.union([z.enum(["all", "auto", "mad", "sigmoidal"]), z.array(z.string())]).optional().default("auto"),
|
|
300
|
-
sigma_f: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
301
|
-
learning_rate: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.union([z.number(), z.object({
|
|
302
|
-
ordering: z.tuple([z.number(), z.number()]),
|
|
303
|
-
convergence: z.tuple([z.number(), z.number()]),
|
|
304
|
-
})]).optional()),
|
|
305
|
-
batch_size: z.number().int().optional(),
|
|
306
|
-
quality_metrics: z.union([z.enum(["fast", "standard", "full"]), z.array(z.string())]).optional(),
|
|
307
|
-
backend: z.enum(["auto", "cpu", "gpu", "gpu_graphs"]).optional().default("auto"),
|
|
308
|
-
output_format: z.enum(["png", "pdf", "svg"]).optional().default("png"),
|
|
309
|
-
output_dpi: z.enum(["standard", "retina", "print"]).optional().default("retina"),
|
|
310
|
-
colormap: z.string().optional(),
|
|
311
|
-
row_range: z.tuple([z.number().int().min(1), z.number().int().min(1)]).optional(),
|
|
312
|
-
cv_folds: z.number().int().min(0).max(20).optional()
|
|
313
|
-
.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."),
|
|
314
|
-
viz_mode: z.enum(["full", "summary", "summary_plus_top"]).optional()
|
|
315
|
-
.describe("Visualization density; auto-capped when feature count > 40"),
|
|
316
|
-
viz_top_components: z.number().int().min(0).max(64).optional()
|
|
317
|
-
.describe("With viz_mode=summary_plus_top: upload top-N component maps by variance (default 8)"),
|
|
318
|
-
emit_cell_uncertainty: z.boolean().optional()
|
|
319
|
-
.describe("train_impute: write imputation_uncertainty.csv (pool_std per imputed cell)"),
|
|
320
|
-
gamma: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
321
|
-
gamma_f: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
322
|
-
siom_decay: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
323
|
-
siom_penalty: z.enum(["linear", "log", "exp"]).optional(),
|
|
324
|
-
penalty_alpha: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
325
|
-
reset_per_epoch: z.boolean().optional(),
|
|
326
|
-
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"),
|
|
327
|
-
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"),
|
|
328
|
-
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)"),
|
|
329
|
-
topology: z.enum(["free", "chain"]).optional().default("free"),
|
|
330
|
-
max_nodes: z.number().int().min(2).optional(),
|
|
331
|
-
effort: z.enum(["quick", "standard", "thorough"]).optional().default("standard"),
|
|
332
|
-
n_initial: z.number().int().min(2).optional(),
|
|
333
|
-
n_passes: z.number().int().min(1).optional(),
|
|
334
|
-
growth_interval: z.number().int().min(1).optional(),
|
|
335
|
-
neighborhood_size: z.number().int().min(1).optional(),
|
|
336
|
-
edge_max_age: z.number().int().min(1).optional(),
|
|
337
|
-
max_degree: z.number().int().min(0).optional(),
|
|
338
|
-
error_threshold: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
339
|
-
error_decay: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
340
|
-
ring_close_threshold: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
341
|
-
sigma_0: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
342
|
-
eta_0: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
343
|
-
eta_f: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
344
|
-
elastic_lambda: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
345
|
-
elastic_mu: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
346
|
-
elastic_anchor: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
347
|
-
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,
|
|
348
172
|
}, async (args) => {
|
|
349
173
|
const { action, job_id, job_ids, dataset_id } = args;
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const params = {
|
|
360
|
-
model: "SOM",
|
|
361
|
-
periodic: false,
|
|
362
|
-
normalize: "mad", // robust normalization
|
|
363
|
-
grid: [side, side],
|
|
364
|
-
epochs: [10, 20],
|
|
365
|
-
batch_size: 32,
|
|
366
|
-
quality_metrics: "standard",
|
|
367
|
-
output_format: "png",
|
|
368
|
-
output_dpi: 2,
|
|
369
|
-
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
|
+
],
|
|
370
183
|
};
|
|
371
|
-
const data = (await apiCall("POST", "/v1/jobs", { dataset_id, params }));
|
|
372
|
-
const jid = String(data.id ?? "");
|
|
373
|
-
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.` }] };
|
|
374
184
|
}
|
|
375
|
-
if (action === "
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
if (e.httpStatus === 401 || e.httpStatus === 403)
|
|
383
|
-
throw e;
|
|
384
|
-
if (preset && grid_x === undefined && epochs === undefined) {
|
|
385
|
-
throw new Error("Could not fetch training config from server, and missing explicit grid/epochs.");
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
if (!dataset_id)
|
|
389
|
-
throw new Error(`jobs(${action}) requires dataset_id`);
|
|
390
|
-
const { params, paramSummary, effectiveGrid } = buildTrainMapParams({
|
|
391
|
-
preset, grid_x, grid_y, epochs, model, periodic, columns, cyclic_features,
|
|
392
|
-
temporal_features, feature_weights, transforms, auto_log_transforms,
|
|
393
|
-
time_delay_embeddings, categorical_features,
|
|
394
|
-
normalize, sigma_f, learning_rate, batch_size, quality_metrics, backend,
|
|
395
|
-
output_format, output_dpi, colormap, row_range,
|
|
396
|
-
}, PRESETS);
|
|
397
|
-
if (action === "train_impute") {
|
|
398
|
-
params._job_type = "train_impute";
|
|
399
|
-
params.model = model ?? "auto";
|
|
400
|
-
params.cv_folds = cv_folds ?? 5;
|
|
401
|
-
if (viz_mode !== undefined)
|
|
402
|
-
params.viz_mode = viz_mode;
|
|
403
|
-
if (viz_top_components !== undefined)
|
|
404
|
-
params.viz_top_components = viz_top_components;
|
|
405
|
-
if (emit_cell_uncertainty !== undefined)
|
|
406
|
-
params.emit_cell_uncertainty = emit_cell_uncertainty;
|
|
407
|
-
// Plain numeric path only — strip unsupported keys if caller passed them
|
|
408
|
-
delete params.cyclic_features;
|
|
409
|
-
delete params.temporal_features;
|
|
410
|
-
delete params.categorical_features;
|
|
411
|
-
delete params.transforms;
|
|
412
|
-
delete params.auto_log_transforms;
|
|
413
|
-
delete params.time_delay_embeddings;
|
|
414
|
-
}
|
|
415
|
-
if (action === "train_siom_map") {
|
|
416
|
-
params._job_type = "train_siom";
|
|
417
|
-
if (gamma !== undefined)
|
|
418
|
-
params.gamma = gamma;
|
|
419
|
-
if (gamma_f !== undefined)
|
|
420
|
-
params.gamma_f = gamma_f;
|
|
421
|
-
if (siom_decay !== undefined)
|
|
422
|
-
params.siom_decay = siom_decay;
|
|
423
|
-
if (siom_penalty !== undefined)
|
|
424
|
-
params.siom_penalty = siom_penalty;
|
|
425
|
-
if (penalty_alpha !== undefined)
|
|
426
|
-
params.penalty_alpha = penalty_alpha;
|
|
427
|
-
if (reset_per_epoch !== undefined)
|
|
428
|
-
params.reset_per_epoch = reset_per_epoch;
|
|
429
|
-
if (siom_feature_geometry !== undefined)
|
|
430
|
-
params.siom_feature_geometry = siom_feature_geometry;
|
|
431
|
-
if (siom_qe_backend !== undefined)
|
|
432
|
-
params.siom_qe_backend = siom_qe_backend;
|
|
433
|
-
if (siom_qe_batch_size !== undefined)
|
|
434
|
-
params.siom_qe_batch_size = siom_qe_batch_size;
|
|
435
|
-
}
|
|
436
|
-
let totalRows = 0;
|
|
437
|
-
try {
|
|
438
|
-
const preview = (await apiCall("GET", `/v1/datasets/${dataset_id}/preview?n_rows=1`));
|
|
439
|
-
totalRows = Number(preview?.total_rows ?? 0);
|
|
440
|
-
}
|
|
441
|
-
catch { /* ignore */ }
|
|
442
|
-
const submitBody = { dataset_id, params };
|
|
443
|
-
if (label && label.trim() !== "")
|
|
444
|
-
submitBody.label = label;
|
|
445
|
-
const data = (await apiCall("POST", "/v1/jobs", submitBody));
|
|
446
|
-
const newJobId = data.id;
|
|
447
|
-
const variantPrefix = action === "train_siom_map" ? "variant=siom"
|
|
448
|
-
: action === "train_impute" ? "variant=train_impute"
|
|
449
|
-
: "variant=som";
|
|
450
|
-
data.effective_params = `${variantPrefix}, ${paramSummary}`;
|
|
451
|
-
try {
|
|
452
|
-
const sys = (await apiCall("GET", "/v1/system/info"));
|
|
453
|
-
const pending = Number(sys.status?.pending_jobs ?? sys.pending_jobs ?? 0);
|
|
454
|
-
const totalEta = Number(sys.training_time_estimates_seconds?.total ??
|
|
455
|
-
(sys.gpu_available ? 45 : 120));
|
|
456
|
-
const waitMinutes = Math.round((pending * totalEta) / 60);
|
|
457
|
-
let msg = `Job submitted (${variantPrefix}, ${paramSummary}). `;
|
|
458
|
-
if (waitMinutes > 1)
|
|
459
|
-
msg += `You are #${pending + 1} in queue. Estimated wait: ~${waitMinutes} min. `;
|
|
460
|
-
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.`;
|
|
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
|
-
catch {
|
|
468
|
-
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.`;
|
|
469
|
-
msg += ` Optional: training_monitor(job_id="${newJobId}") for charts—not required.`;
|
|
470
|
-
if (effectiveGrid && totalRows > 0 && effectiveGrid[0] * effectiveGrid[1] > totalRows * 0.75) {
|
|
471
|
-
msg += ` Note: Grid may be large for ${totalRows} rows (consider grid=auto for fewer dead nodes).`;
|
|
472
|
-
}
|
|
473
|
-
data.message = msg;
|
|
474
|
-
}
|
|
475
|
-
return textResult(data);
|
|
476
|
-
}
|
|
477
|
-
if (action === "train_floop_siom" || action === "train_floop_chain") {
|
|
478
|
-
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;
|
|
479
|
-
if (!dataset_id)
|
|
480
|
-
throw new Error("jobs(train_floop_siom) requires dataset_id");
|
|
481
|
-
const params = {
|
|
482
|
-
_job_type: "train_floop_siom",
|
|
483
|
-
topology,
|
|
484
|
-
effort,
|
|
485
|
-
normalize,
|
|
486
|
-
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
|
+
],
|
|
487
192
|
};
|
|
488
|
-
if (max_nodes !== undefined)
|
|
489
|
-
params.max_nodes = max_nodes;
|
|
490
|
-
if (columns?.length)
|
|
491
|
-
params.columns = columns;
|
|
492
|
-
if (cyclic_features?.length)
|
|
493
|
-
params.cyclic_features = cyclic_features;
|
|
494
|
-
if (temporal_features?.length)
|
|
495
|
-
params.temporal_features = temporal_features;
|
|
496
|
-
if (feature_weights && Object.keys(feature_weights).length > 0)
|
|
497
|
-
params.feature_weights = feature_weights;
|
|
498
|
-
if (transforms && Object.keys(transforms).length > 0)
|
|
499
|
-
params.transforms = transforms;
|
|
500
|
-
if (auto_log_transforms)
|
|
501
|
-
params.auto_log_transforms = auto_log_transforms;
|
|
502
|
-
if (row_range && row_range.length >= 2 && row_range[0] <= row_range[1])
|
|
503
|
-
params.row_range = row_range;
|
|
504
|
-
const dpiMap = { standard: 1, retina: 2, print: 4 };
|
|
505
|
-
if (output_dpi && output_dpi !== "retina")
|
|
506
|
-
params.output_dpi = dpiMap[output_dpi] ?? 2;
|
|
507
|
-
if (colormap)
|
|
508
|
-
params.colormap = colormap;
|
|
509
|
-
if (n_initial !== undefined)
|
|
510
|
-
params.n_initial = n_initial;
|
|
511
|
-
if (n_passes !== undefined)
|
|
512
|
-
params.n_passes = n_passes;
|
|
513
|
-
if (growth_interval !== undefined)
|
|
514
|
-
params.growth_interval = growth_interval;
|
|
515
|
-
if (neighborhood_size !== undefined)
|
|
516
|
-
params.neighborhood_size = neighborhood_size;
|
|
517
|
-
if (edge_max_age !== undefined)
|
|
518
|
-
params.edge_max_age = edge_max_age;
|
|
519
|
-
if (max_degree !== undefined)
|
|
520
|
-
params.max_degree = max_degree;
|
|
521
|
-
if (gamma !== undefined)
|
|
522
|
-
params.gamma = gamma;
|
|
523
|
-
if (siom_decay !== undefined)
|
|
524
|
-
params.siom_decay = siom_decay;
|
|
525
|
-
if (siom_penalty !== undefined)
|
|
526
|
-
params.siom_penalty = siom_penalty;
|
|
527
|
-
if (penalty_alpha !== undefined)
|
|
528
|
-
params.penalty_alpha = penalty_alpha;
|
|
529
|
-
if (error_threshold !== undefined)
|
|
530
|
-
params.error_threshold = error_threshold;
|
|
531
|
-
if (error_decay !== undefined)
|
|
532
|
-
params.error_decay = error_decay;
|
|
533
|
-
if (ring_close_threshold !== undefined)
|
|
534
|
-
params.ring_close_threshold = ring_close_threshold;
|
|
535
|
-
if (sigma_0 !== undefined)
|
|
536
|
-
params.sigma_0 = sigma_0;
|
|
537
|
-
if (sigma_f !== undefined)
|
|
538
|
-
params.sigma_f = sigma_f;
|
|
539
|
-
if (eta_0 !== undefined)
|
|
540
|
-
params.eta_0 = eta_0;
|
|
541
|
-
if (eta_f !== undefined)
|
|
542
|
-
params.eta_f = eta_f;
|
|
543
|
-
if (elastic_lambda !== undefined)
|
|
544
|
-
params.elastic_lambda = elastic_lambda;
|
|
545
|
-
if (elastic_mu !== undefined)
|
|
546
|
-
params.elastic_mu = elastic_mu;
|
|
547
|
-
if (elastic_anchor !== undefined)
|
|
548
|
-
params.elastic_anchor = elastic_anchor;
|
|
549
|
-
if (anchor_percentile !== undefined)
|
|
550
|
-
params.anchor_percentile = anchor_percentile;
|
|
551
|
-
const submitBody = { dataset_id, params };
|
|
552
|
-
if (label && label.trim() !== "")
|
|
553
|
-
submitBody.label = label;
|
|
554
|
-
const data = (await apiCall("POST", "/v1/jobs", submitBody));
|
|
555
|
-
const newJobId = data.id;
|
|
556
|
-
const maxNodeSummary = max_nodes === undefined ? "max_nodes=auto(~2*sqrt(n_samples))" : `max_nodes=${max_nodes}`;
|
|
557
|
-
const paramSummary = [
|
|
558
|
-
"variant=floop",
|
|
559
|
-
`topology=${topology ?? "free"}`,
|
|
560
|
-
maxNodeSummary,
|
|
561
|
-
`effort=${effort ?? "standard"}`,
|
|
562
|
-
gamma !== undefined ? `gamma=${gamma}` : "",
|
|
563
|
-
].filter(Boolean).join(", ");
|
|
564
|
-
data.effective_params = paramSummary;
|
|
565
|
-
data.message =
|
|
566
|
-
`Job submitted (${paramSummary}). Poll with jobs(action=status, job_id="${newJobId}"). ` +
|
|
567
|
-
`When status is completed, use results(action=get, job_id="${newJobId}") to view FLooP-SIOM figures and metrics. ` +
|
|
568
|
-
`Optional: training_monitor(job_id="${newJobId}") for charts—not required.` +
|
|
569
|
-
(action === "train_floop_chain"
|
|
570
|
-
? " Note: action=train_floop_chain is deprecated; prefer train_floop_siom."
|
|
571
|
-
: "");
|
|
572
|
-
return textResult(data);
|
|
573
193
|
}
|
|
194
|
+
// ---- Lifecycle ----
|
|
574
195
|
if (action === "status") {
|
|
575
196
|
if (!job_id)
|
|
576
197
|
throw new Error("jobs(status) requires job_id");
|
|
@@ -626,22 +247,6 @@ export function registerJobsTool(server, description) {
|
|
|
626
247
|
const data = await apiCall("DELETE", `/v1/jobs/${job_id}`);
|
|
627
248
|
return textResult(data);
|
|
628
249
|
}
|
|
629
|
-
if (action === "batch_predict") {
|
|
630
|
-
if (!job_id)
|
|
631
|
-
throw new Error("jobs(batch_predict) requires job_id (parent training job)");
|
|
632
|
-
if (!args.inputs || !Array.isArray(args.inputs) || args.inputs.length === 0) {
|
|
633
|
-
throw new Error("jobs(batch_predict) requires inputs (non-empty array of { dataset_id? or rows? })");
|
|
634
|
-
}
|
|
635
|
-
const body = { inputs: args.inputs };
|
|
636
|
-
const data = (await apiCall("POST", `/v1/results/${job_id}/batch_predict`, body));
|
|
637
|
-
const ids = (data.ids ?? []);
|
|
638
|
-
return {
|
|
639
|
-
content: [{
|
|
640
|
-
type: "text",
|
|
641
|
-
text: `Batch predict: ${ids.length} job(s) submitted. Parent: ${job_id}. Poll each with jobs(action=status, job_id="<id>"). IDs: ${ids.join(", ")}`,
|
|
642
|
-
}],
|
|
643
|
-
};
|
|
644
|
-
}
|
|
645
250
|
throw new Error("Invalid action");
|
|
646
251
|
});
|
|
647
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") {
|